Copy Object Note

对象的深拷贝与浅拷贝

1、对象的引用

1
2
3
var objA = {content:'人间不值得'}
var objB = objA ;
objA === objB // true

上述代码仅仅使用了”=”赋值,这种表达方式为引用对象,不属于浅拷贝,两个对象所用内存地址是相同的,所以判断结果为true。

2、浅拷贝

将对象进行浅拷贝有诸多方式:

1)遍历赋值

1
2
3
4
5
6
var objA = {content:'开心点朋友',contentObj:{content: '人间不值得'}};
var objB = {};
for (var i in objA) {
       objB[i] = objA[i];
}
objA === objB // false

上述代码这种方式,遍历了对象A的每个属性,并将其赋值给对象B,看起来像是在内存中开辟了新的空间给对象B,并且判断全等时,两个对象也是不相等的,很容易被误解成就是深拷贝,但我们接着看:

1
2
objA.contentObj.content = '那哪里值得';
console.log(objB);//{content: "开心点朋友",contentObj: {content: "那哪里值得"}

从上面代码可以看出,虽然两个对象的引用地址不同,但是修改了对象A中类型为对象的属性值,对象B的相应的属性值也发生了变化。不止是对象类型,其他复合类型如数组、函数,都只是拷贝了引用地址,而不是这个值的副本。

2)对象展开运算符

1
2
3
var objA = {content:'开心点朋友',contentObj:{content: '人间不值得'}};
var objB = {...objA};
objA === objB // false

上述代码是将对象A的所有属性搜集整理好之后赋值给对象B,本质上与Object.assign相同。两个对象的引用地址不同。

1
2
3
4
5
var objA = {content:'开心点朋友',contentObj:{content: '人间不值得'}};
var objC = {content:'???'};
objC.__proto__ = objA;
var objB = {...objC};
console.log(objB);//{content:'???'}

上述代码表示,对象展开运算符的拷贝方式只能拷贝对象自身的属性,不能拷贝其原型对象上的属性。

3)Object.assign

1
2
3
var objA = {content:'开心点朋友',contentObj:{content: '人间不值得'}};
var objB = Object.assign({}, objA);
objA === objB // false

上述代码是将对象A克隆一份,赋值给对象B,两个对象的引用地址不同。

3、深拷贝

1)JSON.stringify和JSON.parse

1
2
3
4
5
var objA = {content:'开心点朋友',contentObj:{content: '人间不值得'}};
var objB = JSON.parse(JSON.stringify(objA));
objA === objB // false
objA.contentObj.content = '那哪里值得';
console.log(objB);//{content: "开心点朋友",contentObj: {content: "人间不值得"}}

上述代码是一个深拷贝的过程,可以看到,它的效果是将对象中类型为对象的属性值也重新开辟空间,让两个对象做到了完完全全的不相等,但是,这个做法有一个弊端,就是只支持纯数据JSON的深度拷贝。看下面的例子:

1
2
3
4
5
var objA = {content:function(){console.log('开心点朋友')},contentObj:{content: '人间不值得'},contentTime: new Date(),aa:null,bb:undefined};
var objB = JSON.parse(JSON.stringify(objA));
objA === objB // false

console.log(objB);//{contentObj: {content: "人间不值得"},contentTime: "2018-11-27T11:13:45.361Z"}

我们会发现,这种拷贝方式,会忽略类型为function、undefined、和null的属性值,并且对时间对象支持的也不友好。

而且,这种方式,只能拷贝对象自身的属性,不能拷贝对象继承的属性:

图片

2)其它靠谱方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const deepClone = (obj) => {
    if(obj === null) return null;
    if(typeof obj !== 'object') return obj;
    if(obj.constructor===Date) return new Date(obj);
    if(obj.constructor === RegExp) return new RegExp(obj);
    const newObj = new obj.constructor ();  //保持继承链
    for (let key of obj) {
        if (obj.hasOwnProperty(key)) {   //不遍历其原型链上的属性
            const val = obj[key];
            newObj[key] = typeof val === 'object' ? deepClone(val) : val;
        }
    }
    return newObj;
};

参考文献: 王玉略的个人网站