在 JavaScript 引用数据类型中,变量保存的是一个指向堆内存的指针,当需要访问引用类型(如对象,数组等)的值时,首先从栈中获得该对象的地址指针,然后再从堆内存中取得所需的数据。
1 | let obj1 = { x: 1, y: 2 } |
以上的拷贝方式就是浅拷贝,当 obj2 的值改变时,obj1 的值也随之发生改变。
浅拷贝
1 | let arr1 = [0, 1, ['a', 'b']] |
Array.prototype.concat(), Array.prototype.slice(), Array.from() 只能实现对一维数组的深拷贝。
Object.assign()
1 | let obj1 = { x: 1, y: 2 } |
深拷贝
使用 JSON.parse() + JSON.stringify() 实现深拷贝
1 | let obj1 = { |
JSON.parse 和 JSON.stringify 看起来不错,不过存在一些问题:
- undefined、任意的函数以及 symbol 值,在序列化过程中会被忽略(出现在非数组对象的属性值中时)或者被转换成 null(出现在数组中时)。
- 所有以 symbol 为属性键的属性都会被完全忽略掉。
- 不可枚举的属性会被忽略。
1 | JSON.stringify({a: function add (){}}) // '{}' |
使用递归
1 | function deepClone(o) { |
测试代码:
1 | let obj1 = { |
注意:由于使用 for in 循环,所以只能深度拷贝对象自身属性(非原型链上的属性),并且属性为 enumerable。
使用递归拷贝对象的方法,在目标非常大,层级关系非常深的时候会出现性能问题,具体解决方案可以参考我之前写的 JavaScript递归优化 使用栈代替递归的方式解决。
lodash
lodash 中提供 4 个对象拷贝相关的方法:
1 | _.clone() // 提供浅拷贝 |
demo
1 | function customizer(value) { |
相信上述几种方法已经能够满足我们平时大部分的需求了,如果有额外的需求,只能自己定义实现深/浅拷贝的方式了。