说到深浅拷贝的时候就不得不说一下JS中的变量类型了:
-
基本类型:
undefined
、null
、boolean
、number
、string
, 按值存放在栈内存中的简单数据段, 可以直接访问. -
引用类型: 存放在堆内存中的对象, 变量保存的是一个指向存放数据位置的指针. 访问引用类型(
object
,array
)的值时, 首先从栈中获取到存放该数据位置的指针, 然后再从堆中取得数据. -
浅拷贝: 浅拷贝是拷贝引用, 拷贝后的引用都是指向同一个存放数据位置的指针, 无论操作哪一个都是在操作指向在堆中的那一个数据, 所以双方都会有影响.
-
深拷贝: 在堆中重新分配内存, 将源对象的各个属性复制进去. 对拷贝对象和源对象各自操作互不影响.
浅拷贝分两种情况, 拷贝源对象的引用和源对象拷贝实例, 但其属性拷贝引用. 拷贝源的引用 eg:
let obj1 = {
a: 1
};
let obj2 = obj1;
console.log(obj1 === obj2); // true
obj1.a = 2;
console.log(obj2.a); // 2
源对象拷贝实例, 其属性对象拷贝引用
外层源对象是拷贝实例, 但是如果源对象的属性元素为复杂数据类型时, 内层元素拷贝引用. 对源对象操作时不影响拷贝对象. 但对其属性操作时, 会改变拷贝对象属性的值. 常用方法: Array.prototype.slice()
, Array.prototype.concat()
, jQuery
的$.extend({}, obj)
, Object.assign()
等. eg:
let obj1 = {
a: 1,
b: [1, 2, 3],
c: {
c1: 1
}
};
let obj2 = Object.assign({}, obj1);
obj2.a = 2;
obj2.b[0] = 2;
obj2.c.c1 = 10
console.log(obj1);
/**
输出结果:
{
a: 1,
b: [2, 2, 3],
c: {
c1: 10
}
}
**/
这上面的例子中obj1
为源对象, obj2
为拷贝对象. 拷贝后修改拷贝对象的属性a
没有影响到源对象, 修改拷贝对象的复杂数据类型属性b
, c
后影响到源对象.
深拷贝过后, 源对象、拷贝对象包括其内部元素(包括复杂类型元素)都不互相干扰. 常见方法有JSON.parse(JSON.stringify(obj))
, jQuery
的$.extend(true, {}, obj)
, lodash
的_.cloneDeep
和_clone(value, true)
. eg:
let obj1 = {
a: 1,
b: [1, 2, 3],
c: {
c1: 1
}
};
let obj2 = JSON.parse(JSON.stringify(obj1));
obj2.a = 2;
obj2.b[0] = 2;
obj2.c.c1 = 10
console.log(obj1);
/**
输出结果:
{
a: 1,
b: [1, 2, 3],
c: {
c1: 1
}
}
**/
最后输出源对象(obj1
) 得到结果是源对象未变化.
深拷贝就是增加一个指针(栈内存)申请一个新的堆内存, 并让这个指针指向这个堆内存. 当我们需要复制源对象而又不能修改源对象的时候, 深拷贝就是你想要的.
那么是时候自己实现一个深拷贝方法了:
function deepclone(sObj, cObj) {
cObj = cObj || (Array.isArray(sObj) ? [] : {});
for(let i in sObj) {
if(typeof sObj[i] === 'object') {
cObj[i] = Array.isArray(sObj[i]) ? [] : {};
deepclone(sObj[i], cObj[i])
} else {
cObj[i] = sObj[i]
}
};
return cObj;
}