1.总述
在JavaScript中深浅拷贝均针对于引用数据类型。为了验证各种拷贝,准备三层深入结构的Object对象类型数据。
var obj = {
level1stu:[
{name:"frank1", girlFriends:["迪丽肉巴1","古力nong扎1","玛尔扎哈1"]},
{name:"frank2", girlFriends:["迪丽肉巴2","古力nong扎2","玛尔扎哈2"]},
{name:"frank3", girlFriends:["迪丽肉巴3","古力nong扎3","玛尔扎哈3"]}
],
level2stu:[
{name:"oycat1", girlFriends:["小战1","王九博1","李难峰1"]},
{name:"oycat2", girlFriends:["小战2","王九博2","李难峰2"]},
{name:"oycat3", girlFriends:["小战3","王九博3","李难峰3"]}
]
};
2.由浅入深实现各种拷贝
2.1 浅拷贝
浅拷贝,顾名思义就是拷贝浅层(浮于表面的东西)。在JavaScript中具体表现为就是仅仅将变量保存的内存地址赋值给另一个变量,也就是说两个变量内保存的值是相同的内存地址,故而两个变量仍旧指向相同的内存地址。所以牵一发而动全身,对一个变量操作,另一个变量指向的内存地址中的值当然跟随发生变化。
var obj1 = obj
obj1.level1stu[0].girlFriends[0] = "迪吧哈哈";
console.log(obj);
console.log(obj1);
2.2 "深一层"的浅拷贝
"深一层"的浅拷贝,本质上来说并没有这种拷贝方式,严格来讲这玩意儿应该算是一种失败的深拷贝操作。但是这种带有缺陷的拷贝对于理解深拷贝的具体运作机制有帮助,所以姑且写来感受一下。
var obj1 = {};
for (let key in obj) {
obj1[key] = obj[key];
}
obj1.level1stu = 0; //完成深拷贝一层
console.log(obj);
console.log(obj1);
代码中能够明显看到,拷贝操作不再是单纯的创建变量-赋值操作。而是通过循环的方式,在obj1对象中不断地使用新变量obj1[key]来接收原对象obj中与之对应每一个对应值obj[key]。可以理解为上面的操作拆分开来的话:
obj1 [ level1stu ] = obj [ level1stu ];
obj1 [ level2stu ] = obj [ level2stu ];
这样的确实现了 obj1[ level1stu ] 和 obj [ level1stu ] 两个变量所保存的内存地址不同的目的;
但是在更深一层中: obj1[ level1stu ][0] 与 obj[ level1stu ][0]、obj1[ level1stu ][1] 与 obj[ level1stu ][1] 这样对应的变量中所保存的内存地址却仍然相同。仍旧存在牵一发而动全身的现象,也就是说仍旧不算是深拷贝。
//obj1.level1stu = 0; //完成深拷贝一层
obj1.level1stu[0].girlFriends = 0; //两层直接gg
console.log(obj);
console.log(obj1);
2.3深拷贝
因为对象的内部嵌套层数有可能是"无限的",因此我们在不清楚究竟会有多少层结构的情况下,递归就是我们解决问题的最好办法。
function deepCopy(obj){
//判断如果要深拷贝的是对象,就创建空对象,否则创建数组。
var newObj = Array.isArray(obj) ? [] : {};
//设置递归结束条件,如果不是引用类型就结束
if(typeof obj === "object"){
//遍历每一个属性
for (let key in obj) {
//如果属性仍旧是引用类型,那就递归
//如果属性是基本数据类型,那就直接复制
if(typeof obj[key] === "object"){
newObj[key] = deepCopy(obj[key]);
}else{
newObj[key] = obj[key];
}
}
}
//递归结束,返回生成对象
return newObj;
}
var obj1 = deepCopy(obj);
obj1.level1stu[0].girlFriends[0] = "迪吧哈哈"
console.log(obj);
console.log(obj1);
针对拷贝对象的操作,对原对象不会产生任何影响。不管操作多深的层级数据,都没有问题。
3.总结
本文没有考虑对象属性中存在相互引用的情况,在相互引用情况下使用递归无疑会造成死循环,需要额外注意。