You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
functionparse(param){returnJSON.parse(JSON.stringify(param))}vara={}varb={}a['b']=bb['a']=aconsole.log(parse(a))//TypeError: Converting circular structure to JSON at JSON.stringify
// Check for circular references and return its corresponding clone.stack||(stack=newStack);varstacked=stack.get(value);if(stacked){returnstacked;}stack.set(value,result);
这里的 value 和 result 分别是是一次遍历中 要拷贝的值 和 拷贝的结果。stack 是一个用来储存每次对应的 value 和 result 的对象, stack下有一块用于储存的数组结构,该数组的每一项记录了单次遍历中的 value 和 result,后二者再次以数组的形式存储,以 value 做为下标 0 的项,result 为下标 1 的项(这里不用对象的 key-value 形式可能是因为循环引用的变量无法使用 JSON.stringify 转换成字符串,只能 toString 转成 object Object);stack 是做为参数贯穿整个遍历过程的,每次遍历时都会以当前的 value 值进行查找(这里的查找直接是判断内存地址相等),如果能在 stack 中查到到对应的结果,则直接返回记录中的result,不再继续递归。
好了,循环引用的问题我们解决了,鼓掌!但是我也放弃使用 JSON 方法了...还有没有其他直接点的方法呢?
问题
由于 js 的传参方式有时会遇到这样的场景:
我只是想继承参数的部分数据,并在此基础添加一些东西,但是参数
data
的源数据也被我改动了,如果之后有其他人想要从data
获取数据,他可能还需要注意是否有像setTime
这样的函数调用它。一点修改
嗯,或者你也可以用

for...in
,注意下二者的不同。我们知道
Object.assign
只是浅拷贝,如果data.obj
的属性值仍然有引用类型的话,那么还是会遇见同样的问题。那要怎么办?难道要遍历
data
下每个属性的值?一个个复制过来?我们看看lodash
是怎么做的你猜的没错,的确是要深度遍历的。
在
baseClone
方法内,拿到要拷贝的对象value
后,先检查其类型,然后由对应的 handler 来处理,比如value
是数组类型,则使result
为同样长度的数据,然后对每一项都递归调用baseClone
,直到value
是非引用类型,返回value
的值;如果是普通对象类型,则使result
为空数组,然后拿取value
的key
,对每个key
的赋值也是递归调用baseClone
。想要简单点
难道我深拷贝一个变量还要引入 lodash 这么麻烦吗 ?没有简单点的办法吗?
嗯,可能有点不是那么酷炫,但是他确实可以满足要求,而且也无须引入其他的库。但如果它真的这么完美,为什么 lodash 不这么写呢?
的确,它的缺点还挺多的,这里取几个我觉得比较重要的:
{}
是啊,毕竟
JSON
的两个方法本身就只是用来转换 js 内的对象为 JSON 格式的,上述几点甚至都不是缺点,是我们想借用其他方法做深拷贝时遇到的问题。既然是问题那应该可以解决吧,比如第一条和第二条,在
stringify
时判断类型,转化成 带类型标识符的对象字符串如:Set [1,2,3,4,5]
,然后在parse
的时候对字符串进行解析,特别的类型调用对应的构造函数... 听起来变得更麻烦了,没关系,忍忍把各个类型的处理都写了;针对第三条,抛错了?没关系,我 try catch 包起来...,什么?循环引用?循环引用?
如上代码, 变量
a
和b
互相引用对方,此时如果借用 JSON 的方法来进行深拷贝的话,会报循环结构转换转换 JSON 错误。这个问题怎么解决呢?我们再翻出 lodash 的源码看看...这里的
value
和result
分别是是一次遍历中 要拷贝的值 和 拷贝的结果。stack
是一个用来储存每次对应的value
和result
的对象,stack
下有一块用于储存的数组结构,该数组的每一项记录了单次遍历中的value
和result
,后二者再次以数组的形式存储,以value
做为下标 0 的项,result
为下标 1 的项(这里不用对象的 key-value 形式可能是因为循环引用的变量无法使用 JSON.stringify 转换成字符串,只能 toString 转成 object Object);stack
是做为参数贯穿整个遍历过程的,每次遍历时都会以当前的value
值进行查找(这里的查找直接是判断内存地址相等),如果能在stack
中查到到对应的结果,则直接返回记录中的result
,不再继续递归。好了,循环引用的问题我们解决了,鼓掌!但是我也放弃使用 JSON 方法了...还有没有其他直接点的方法呢?
其他方法
结构化克隆算法是由HTML5规范定义的用于复制复杂JavaScript对象的算法,它通过递归输入对象来构建克隆,同时保持先前访问过的引用的映射,以避免无限遍历循环。
怎么用?
emmm... 它还不能直接使用,你得依靠一些其他的 API ,间接的使用它。
postMessage()
什么??还是异步的... 不,我希望能使用同步的方法使用它。
history()
如你所见,我们要借用一下
history.replaceState
这个方法,但是我们不能改变history
原有的状态,所以用完就要恢复原状,当无事发生过。至少,这是个同步的方法...,如果是同步的场景可以考虑一下...
性能展示
这里的测试代码是使用的 [Deep-copying in JavaScript] (https://dassur.ma/things/deep-copy/) 一文中的,并再次基础做了一些修改。
结果!
(很懒就不画图表了)单位 μs
(缪斯),计算时间的用的接口是performance.now()
结果精确到5微秒。chrome

safari

...em...Safari浏览器在调用完 postMessage 方法后就...没有然后了...表格都没刷出来...等了 40 s 终于刷出第一栏...
注释完
postMessage
又发现不能频繁的调用 history 。...em.. 调用 history 相关 api 对 firefox 好像压力很大,以至于循环都有些错乱...于是注释了相关代码
就结果而言好像看不出什么区别,可能是我的数据不好,大家可以去看看原文,有展示阅读性更好的图表,尽管没有 lodash 就是了。
结果
回到我们最初的问题,我们只是想深拷贝一个 js 对象,如果只是一个比较"普通"的对象,用JSON的方法简单又快捷,但是如果这个对象有些“复杂”,似乎使用 lodash 的方法是比较好的选择,而且 lodash 连 Structured Clone 算法忽视的 symbol 类型 和 Function 也考虑其中,兼容性也没问题,也不会在不同的浏览器发生意外的状况...
lodash 万岁!lol!!
参考阅读:
Deep-copying in JavaScript
The text was updated successfully, but these errors were encountered: