这是我参与8月更文挑战的第6天,活动详情查看:8月更文挑战
深拷贝与浅拷贝
浅拷贝:
创建一个新的对象,这个对象身上有着原始对象的所有属性值。如果属性是基本类型,则拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址。当其中一个对象的属性改变了,另一个对象也会受到影响。 下面就是一个浅拷贝的方法:
function shallowClone(target) {
let cloneTarget = {};
for (const key in target) {
cloneTarget[key] = target[key];
}
return cloneTarget;
};
深拷贝:
深拷贝就是将一个对象从内存中完整的拷贝一份,在堆内存中重新开辟一块新的空间存放新对象,这样修改其中一个对象就不会影响另一个对象。
深拷贝的方法
1.JSON.parse(JSON.stringify());
在平常的工作中,我们经常会使用到深拷贝的方法,我们使用最多的就是下面这个方法。
JSON.parse(JSON.stringify());
这种写法非常简单,使用得非常普遍,它的确是可以实现对象的深拷贝,可以满足工作中的绝大多数场景,但是它有很多的缺陷。比如:
-
会忽略
undefined -
会忽略
symbol -
不能拷贝函数
-
不能解决循环引用的对象
看一下下面的例子:
let a = {
age: undefined,
sex: Symbol('male'),
jobs: function() {},
name: 'jack'
}
let b = JSON.parse(JSON.stringify(a))
console.log(b) // {name: "jack"}
在上面的这种情况,这种深拷贝的方法会忽略到函数和undefined、symbol,这样就是出现问题。
2. $.extend()
在项目中我们经常会使用到jQuery,其实jQuery中的$.extend()也可以实现深拷贝。
let obj = {
a:1,
b:2,
c: {
x: 0,
y: 1
}
};
let cloneObj = $.extend(true, {}, obj);
obj.c.x = 666;
console.log(cloneObj);
在上面的例子中,我们使用jQuery的$.extend()方法深拷贝了一份obj对象,然后修改了里层的对象,发现并不会互相影响,所以$.extend()也可以实现深拷贝,只不过需要依赖jQuery库。
手动实现深拷贝的方法
深拷贝的原理也很简单,我们只需要创建一个新的对象,然后将原始对象上的属性依次添加到新的对象上,如果是基本类型的话就直接添加到新对象上,如果是引用类型的就递归直到属性为基本类型。于是我们可以实现一个简单的深拷贝方法:
function _deepClone(target) {
if (typeof target === 'object') {
let cloneTarget = Array.isArray(target) ? [] : {};
for(key in target) {
cloneTarget[key] = _deepClone(target[key]);
}
return cloneTarget
} else {
return target
}
}
但是这个依然存在循环引用的问题,比如下面这个例子:
const target = {
field1: 1,
field2: undefined,
field3: {
child: 'child'
},
field4: [2, 4, 8]
};
target.target = target;
let res = _deepClone(target);
console.log(res);
当我们执行上面代码的时候,控制台会抛出错误:Uncaught RangeError: Maximum call stack size exceeded,原因就是上面的对象存在循环引用的情况,即对象的属性间接或直接的引用了自身的情况。
为解决循环引用问题,我们可以额外开辟一个存储空间,来存储当前对象和拷贝对象的对应关系,当需要拷贝当前对象时,先去存储空间中找,有没有拷贝过这个对象,如果有的话直接返回,如果没有的话继续拷贝,这样就巧妙化解了循环引用的问题。
function _deepClone(target, map = new Map()) {
if (typeof target === 'object') {
let cloneTarget = Array.isArray(target) ? [] : {};
if (map.get(target)) {
return map.get(target);
}
map.set(target, cloneTarget);
for (const key in target) {
cloneTarget[key] = _deepClone(target[key], map);
}
return cloneTarget;
} else {
return target;
}
};
这里实现的深拷贝方法只是一个比较简单的深拷贝,其实实现一个真正的深拷贝是很困难的,我们需要考虑好多种情况,比如原型链如何处理、DOM 如何处理、函数怎么处理等等,因为本人水平有限现在只能理解到这里,后续会慢慢实现更加完整的深拷贝方法。