How to Clone
日常开发中会涉及到对象拷贝相关的运用,怎么拷贝是个值得探讨的问题。
浅拷贝
JavaScript一共有七种基础类型,分别为string,number,boolean,null,undefined,symbol,bigint,浅拷贝遇到基础类型的值会直接重新拷贝该值,而对于object类型的值,则会拷贝该值的引用地址。
Object.assign()
我们可以使用Object.assign({}, targetObject)来进行浅拷贝,如下所示:
1 | const bar = { |
可以观察到对拷贝之后的newBar.bar进行重新赋值操作,依然可以影响到原来的bar.bar,这也是浅拷贝的主要问题。
Object Spread Operation
我们也可以使用对象扩展运算符来进行浅拷贝,如下所示:
1 | const bar = { |
总的来说,浅拷贝使用简单明了,但是对于非基础类型值的处理而导致的问题,往往让我们放弃浅拷贝而转用深拷贝。
深拷贝
深拷贝则没有上述浅拷贝带来的问题,对于object等类型的值的处理是重新生成相同的值,这样新值和旧值不会相互影响,解决了浅拷贝的问题。
JSON.parse(JSON.stringify(targetObj))
使用JSON相关的方法来对一个对象深拷贝是常用的深拷贝方法之一,优点是调用简单,且速度较快,如下所示:
1 | const bar = { |
缺点也比较多,比如对递归引用的值无法处理,以及对Set,Map,Date等类型的对象无法处理等,但是一般来说后端返回的内容不会涉及到如上数据类型,所以JSON.parse(JSON.stringify(targetObj))适用于大部分的情况。
Recursive
当然我们也可以使用递归的思想来解决深拷贝的问题,如下所示:
1 | const bar = { |
可以看到deepClone简单实现了一个深拷贝,当然这里的deepClone还有很多情况没有考虑,比如对Set等数据结构的处理,这种情况下我们可以借助第三方库如lodash实现,感兴趣的可以看一下lodash关于这块的处理cloneDeep(obj)
structuredClone()
那么有没有比较简单的原生支持的深拷贝,既能避免JSON.parse(JSON.stringify(targetObj))不能处理Set,Map等数据结构的问题,又不需要引入lodash等三方库呢?答案是有的,那就是structuredClone()。
structuredClone()目前只有 Firefox 94,Deno 1.14 以及 Node.js 17.0.0 支持,但是实际上相关算法已经在较早之前运用到了 MessageChannel,Notification等地方,以MessageChannel为例:
1 | function structuredClone(originObj) { |
可以看到structuredClone()可以正常的处理Set,Date等数据结构,并且成功进行了深拷贝。
总结
- 日常开发中
JSON.parse(JSON.stringify(targetObj))基本上可以应对大部分情况,且较为高效 - 涉及到
Map,Set等数据结构的情况下,考虑使用structuredClone(targetObj) - 最后我们仍然可以借用
lodash等三方库帮我们处理深拷贝问题