首先,值的拷贝,通常有三种方式,由于基本类型与引用类型在内存中存储位置和存储方式的不同,导致了以下三种概念的衍生:
- = 赋值:多个指针指向的是同一个堆中的地址,所以相互有影响;
- 浅拷贝:在堆中重新创建内存,拷贝前后基本数据类型不受影响,但只拷贝一层,无法拷贝子对象;所以改变浅拷贝得到的对象中的引用类型时,原始数据会受到影响;例如数组的concat和slice方法;
- 深拷贝:对子对象也可以拷贝,拷贝前后两个对象互不影响,是对对象以及对象的所有子对象进行拷贝;思路就是递归调用浅拷贝的逻辑,把所有属于对象的属性类型都遍历赋给另一个对象即可。完全的拷贝一个对象,即使嵌套了对象,两者也相互分离,修改一个对象的属性,也不会影响另一个。
看下数组结构的拷贝:
var new_arr = JSON.parse( JSON.stringify(arr) );复制代码
但此法无法拷贝函数。concat、slice、JSON.stringify 都算是技巧类,可以根据实际情况适当使用。
初步实现一个浅拷贝:
在看开源项目的过程中,经常会看到类似如下的源码。for...in
循环对象的所有枚举属性,然后再使用hasOwnProperty()
方法来忽略继承属性。
const shallowClone = (obj) => { if (typeof obj !== 'object') return let newObj = obj instanceof Array ? [] : {} for (let key in obj) { if (obj.hasOwnProperty(key)) { newObj[key] = obj[key] } } return newObj}复制代码
初步实现一个深拷贝:
const deepClone = (obj) => { if (typeof obj !== 'object') return let newObj = obj instanceof Array ? [] : {} for (let key in obj) { if (typeof obj[key] === 'object') { newObj[key] = deepClone(obj[key]) } else { newObj[key] = obj[key] } } return newObj}复制代码
深拷贝会完全的克隆一个新对象,但因为使用递归,性能会不如浅拷贝,在开发中,还是要根据实际情况进行选择。
第三方库的实现
Underscore _.clone()
实际上是一种浅复制 (shallow-copy),所有嵌套的对象和数组都是直接复制引用而并没有进行深复制。源码:
// Create a (shallow-cloned) duplicate of an object._.clone = function(obj) { if (!_.isObject(obj)) return obj; return _.isArray(obj) ? obj.slice() : _.extend({}, obj);};复制代码
jQuery $.extend()
var x = { a: 1, b: { f: { g: 1 } }, c: [ 1, 2, 3 ]};var y = $.extend({}, x), //shallow copy z = $.extend(true, {}, x); //deep copyy.b.f === x.b.f // truez.b.f === x.b.f // false复制代码
lodash _.clone() / _.cloneDeep()
在lodash中关于复制的方法有两个,分别是_.clone()
和_.cloneDeep()
。其中_.clone(obj, true)
等价于_.cloneDeep(obj)
。
jQuery 无法正确深复制 JSON 对象以外的对象,而 lodash 花了大量的代码来实现 ES6 引入的大量新的标准对象。lodash 针对存在环的对象的处理也是非常出色的。因此相较而言,lodash 在深复制上的行为反馈比前两个库好很多。
参考源:
https://juejin.im/post/59ac1c4ef265da248e75892b
https://segmentfault.com/a/1190000002801042
https://github.com/mqyqingfeng/Blog/issues/32