这是我参与「第四届青训营 」笔记创作活动的第5天。
因为以前没有接触过js,js是现学现卖的,所以可能有不对的地方请大佬指教。
问题的开始
在我们的项目中,我们创建了一些对象,这些对象有着不同的内存地址存储着。我们有些需求复制对象。
let user3 = { name: 'John', size: { height: 182, width: 50, }};
字面量赋值
最开始的想法是直接用字面量赋值的方式来进行复制。
let clone = user3;
但是这样是显然不行的,因为js中对象并不是“原始值类型”,这样复制的话是无法复制过来的。以我的理解是user3相当于一个C语言中的指针,它只是把指针给了clone,让它指向该对象的地址。这样并没有实现复制。
用Object.assign()方法实现赋值
所以我查阅了一些资料,发现可以用Object的assign方法实现复制。
let clone = Object.assign({}, user3);
console.log( clone.name ); // John
console.log( clone.size.weight ) // 182
上面代码说明了我们完成了复制,但是紧接着我发现了一个问题。
console.log(user3.size === clone.size); //true
user3.size.height++; //改变size对象中的height
console.log(clone.size.height); //183,也就是说clone只是一个引用变量。
我改变的明明是user3中的size对象的属性,但clone中的size对象的属性也随之改变。所以这个方法并不能完全的把对象属性是对象的值复制过来,到头来还是复制的引用。
经过我的猜测Object.assign()方法大概是这样实现。
for (let objectValue in user3) {
clone[objectValue] = user3[objectValue];
}
所以对于属性是对象的话,这样的方法是不行的。
实现真正的深度复制
但是这时候我灵光乍现,突然有个大概的想法,能不能用递归的方式去找对象的属性是否有对象。有的话就去找对象属性 中的 对象属性 是否 有对象。 直到找到一个对象属性的值全为原始类型为止。然后再层层往回return完成复制。接着我就对代码完成了大概的实现。
// 允许克隆多个对象
function cloneDeep(cloneObj, ...permission) {
length = permission.length;
for (let i = 0; i < length; i++) {
cloneObj = copyObj( cloneObj, permission[i] );
}
return cloneObj;
}
-
首先我使用了扩展运算符来接收将要复制的对象,也就是说此函数是支持多个对象属性复制到一个对象中的。这些对象会存储到permission这个数组中,可以通过数组下标来访问输入的对象。
-
接着遍历数字,让每个对象的属性都复制到cloneObj中。
-
在循环中会用到copyObj这个函数,实现的方法如下
-
function copyObj(dest, src) { for (let key in src) { // 判断当前的属性是否为对象 if (typeof src[key] === 'object') { // 新创建一个对象来存储src[key]中的值 let temp = {}; // 是对象就再次调用copyObj函数用,直到找到一个对象的所有属性是原始值为止。 buf = copyObj(temp, src[key]); dest[key] = buf; } else { dest[key] = src[key]; } } // 当找到一个对象的属性全为原始值并且属性已经添加到dest中后返回引用。 return dest;} -
首先我们会遍历src中的键,看看键中有没有对象。
-
如果我们找到了对象
-
先创建一个空的对象用来存储当前键所对应对象的属性
-
然后再次调用copyObj函数,将空对象和当前键所对应对象的属性传入
-
函数会将复制完的值返回给buf变量,然后将buf变量的引用传递给目标的相关键就完成了!
-
如果我们的值是原始类型,那就直接复制就好了。
let user4 = {};
user4 = cloneDeep(user4, user3); //实现深拷贝 user4.size.width++; console.log(user4.size.width); //51 console.log(user3.size.width); //50 成功完成深拷贝
我们的对复制完以后的user4进行操作,将它size属性中的width加1。然后我们打印出user3.size.width的值。发现值是不同的,这就证明它们没有操控同一块内存,我们的深拷贝成功了!