首先,想真正理解深浅拷贝,需要清楚地知道堆和栈,那么你知道简单数据类型和引用数据类型在堆和栈的关系吗?
栈:基本数据类型的值,引用数据类型的地址。
堆:引用数据类型的值。
我画个图,便于你理解。堆空间远远大于栈空间,可以发现,当数据类型为简单数据类型时,它的值和地址都在堆中,所以一般对于深拷贝浅拷贝都相对于引用数据类型来说的,如果严格区分基本类型的复制,也可以说属于深拷贝。所以,可以说无论哪种数据类型,存储其值所声明的变量都存在于栈空间!
引用数据类型在使用“=”时,赋值的是栈空间中的地址。
注意:尽量避免使用“=”对引用数据类型进行拷贝;修改一个对象,也会影响另一个对象。
浅拷贝:对于对象和数组引用数据类型赋值时,只是把地址复制过去,一个值变化也会影响另一个值变化。(看下面图片再来看看这句话哦!!)
上例子:var obj = {
uname: "张三",
age: "18"
}
var obj2 = obj;
obj2.uname = "李四"
console.log("obj:", obj);
console.log("obj2:", obj2);
深拷贝:对于对象和数组引用数据类型赋值时,如果我们把值赋过去(产生了一个新的值),此时,不影响另一个值变化。
上例子:var obj = {
uname: "张三",
age: "18",
address: "北京",
aiHao: ["吃", "喝"],
car: {
}
}
var obj2 = {};
for (var key in obj) {
console.log("key:", key); // 属性名
console.log(obj[key]); // 属性值
// 对象[属性名] = 属性值
obj2[key] = obj[key]
// obj2['aiHao'] = ["吃", "喝"]
}
obj.age = 20
obj.aiHao.push("玩")
console.log("obj:", obj);
console.log("obj2:", obj2);
这里我用了for...in 通过遍历对象并声明obj2(空对象)来盛装对象中每一个属性,先别急,我知道还有其他方法,我后面会做总结喔!!!这里只是抛砖引玉,你会发现上面例子只能实现含有一层引用数据类型的对象,那么对象里面可能还有多层嵌套(对象中还有包含多个数组/对象的数组/对象),又该怎么办呢?
上例子: var obj = {
uname: "张三",
age: "18",
address: "北京",
aiHao: ["吃", "喝", []],
car: {
jiaGe: "100",
pinPai: "bmw"
},
say: function () { }
}
var objStr = JSON.stringify(obj)
var obj2Str = objStr
var obj2 = JSON.parse(obj2Str);
obj.uname = "李四";
obj.aiHao.push("玩");
console.log("obj:", obj);
console.log("obj2:", obj2); //fn被忽略
这里是通过json方法实现的深拷贝,
但是!!!有两种情况会出现bug:第一种情况,使用json方法实现深拷贝不能拷贝函数,因为当拷贝的数据中有函数类型,会自动忽略,打印时候发现根本没有拷贝到fn;第二种情况如下,牵扯到循环引用(什么是循环引用,我在下面打印一下,你就知道了)
下面是循环引用的样子:
那么有没有很完美的深拷贝呢,我先写一种自己封装的深拷贝函数,这种比较好理解,后面还有另一种,往下找哦!!!:
最后,我来总结一下我自己总结的全部的深拷贝方案以及优缺点:
第一种:
深拷贝的第一种方案(展开运算符实现) 特点:能实现一层拷贝,如果有多层,实现不了。
第二种:
深拷贝的第二种方案(上面我提到的json实现) ,能实现多层拷贝,如果对象中有方法,会把对象中的方法丢掉,而且不能实现循环引用,会报错,详细见上文哦!!!。
第三种(不正规):
深拷贝的第三种方案(通过原型实现的),表现像深拷贝,从内存的角度分析是原型实现,深层次的也实现不了。但是效果确实实现了。
第四种:
自己手写封装深拷贝(看上图,有详细解释哦!!!)
第五种:
使用第三方的库lodash(可以去官网找)实现
第六种:
这个也是自己封装的,但是比上面一种简练,方法不一样。
function shen(a) {
var x = a instanceof Array ? [] : {}
for (var i in a) {
x[i] = a[i] instanceof Object ? arguments.callee(a[i]) : a[i]
}
return x
}
var obj3 = shen(obj)
obj3.name = "赵六"
obj3.data.msg = '失败'
console.log('obj3:', obj3); // 深拷贝,完美复刻,任意修改,完全不影响原数据
第七种:
这种牵扯到底层原理,小伙伴们研究看看哦!!!
function structuralClone(obj) {
return new Promise(resolve => {
const { port1, port2 } = new MessageChannel()
port1.postMessage(obj)
port2.onmessage = e => resolve(e.data) // e是MessageChannel构造函数本身
})
}
obj.data.d = obj.data
// 注意该方法是异步的 // 可以处理 undefined 和循环引用对象
const test = async () => {
const obj5 = await structuralClone(obj)
console.log('obj5',obj5)
}
test()