在学习JS过程中,深克隆和浅克隆是我们一定会遇到的知识点,之前也学习过,但是如何去实现一个深复制和浅复制并没有去实践。今天就来剖析一下他们的概念和实现的具体操作
1.浅复制
简单来说就是复制该对象的指针而不是复制该对象,两个指针指向同一个地址(数据)
// 方法一
let obj1 = {name: "Bob"};
let obj2 = obj1;
console.log(obj2) // {name: "Bob"}
//方法二 使用 ES6 的新方法进行复制
let obj1 = {name: "Bob"};
let obj2 = Object.assign({}, obj1) //Object.assign()用于合并对象
console.log(obj2) // {name: "Bob"}
上面的代码为浅复制,obj2
直接复制了 obj1
的指针(此时两个对象共同指向同一个内存) ,所以此时若是改动任意对象的属性,那么两个对象的属性都会改变。
注意:使用Object.assign()
复制对象时,第一层为深复制(主要是第一层为原始值)
再来看看下面的例子
let obj1 = {
name: "bob",
age: 18,
sex: "man",
}
let obj2 = {}
for(let key in obj1){
obj2[key] = obj1[key] // 可以使用 Object.assign() 方法代替
// 将 obj1 的所有属性遍历并赋值给 obj2
}
obj1.name = "Tom"
console.log(obj1)// obj1 = {name: "Tom", age: 18, sex: "man"}
console.log(obj2)// obj1 = {name: "bob", age: 18, sex: "man"}
上面的代码可以看到,我们将 obj1
的所有属性遍历并赋值给 obj2
。
这时,改变其中一个对象的属性,并不会影响到另一个对象的属性。
注意:它仍然是一个浅复制,因为它只对第一层进行了遍历赋值,如果对多层嵌套的属性进行操作(也可以理解为引用值),依然无能为力,如下
let obj1 = {
name: "bob",
age: 18,
sex: "man",
son:{
one: "Funk"
} // 此属性也是一个对象
}
let obj2 = {}
for(let key in obj1){
obj2[key] = obj1[key]
// 将 obj1 的所有属性遍历并赋值给 obj2
}
obj1.son.one = "Tom"
console.log(obj1)// obj1 = {name: "Tom", age: 18, sex: "man", son:{one: "Tom"}}
console.log(obj2)// obj1 = {name: "Tom", age: 18, sex: "man", son:{one: "Tom"}}
上面可以看出,在进行第二层代码的操作时,改变一个对象的属性时,另一个对象的属性也一并改变了。
弄清楚浅复制之后,我们来封装一个浅复制的功能函数
function shallowCopy(origin, target){
for(let key in origin){
if (origin.hasOwnProperty(key)) { // 过滤掉原型的属性,只复制对象本身的属性
target[key] = origin[key]
}
}
}
let obj1 = {state: "复制成功"}
let obj2 = {}
shallowCopy(obj1, obj2) // 调用浅复制的封装函数
console.log(obj2) // {state: "复制成功"}
封装成功
2.深复制
理解了浅复制的概念,那么深复制就更加容易理解了。
简单来说就是直接复制值,而不是复制值的指针
之前对浅复制我封装了一个函数,如果只看第一层,也可看做是一个深复制操作。
所以说,如果要封装深复制的操作,就等于给每一层都进行这样的操作(递归)
如下:
function deepCopy(origin, target){
for(let key in origin){
if (origin.hasOwnProperty(key)) {
// 过滤掉原型的属性,只复制对象本身的属性
if (typeof(origin[key]) === 'object' && origin[key] !== null) {
// 判断属性是否为引用值。null也是对象。
if (Object.prototype.toString.call(origin[key]) === '[object Array]') {
//判断属性是否为数组
target[key] = [];
}else{
target[key] = {};
}
deepCopy(origin[key], target[key])
// 若属性为引用值,再次调用此函数,处理该属性
}else{
target[key] = origin[key]
// 值为原始值,直接复制函数属性
}
}
}
}
封装完成,此时可以带入对象进行复制操作
下面来归纳一下深复制操作的核心思想
深复制本质上就是直接复制值,而不是引用地址。
而上面深复制的封装函数的核心思想,就是对Object
的每一层都进行赋值。
步骤如下:
for in
循环,遍历所有对象的属性- 对遍历的属性进行判断,使用
origin.hasOwnProperty(key)
,过滤掉原型的属性,只复制对象本身的属性 - 再判断属性是否为引用值(也就是对象)。若不是引用值,则直接复制该属性。若是引用值,则执行完
第4步
后再次调用这个函数(递归) - 再使用
Object.prototype.toString.call(origin[key])
判断属性是否为数组。