前言
在 javascript 中有不同的方法来复制对象,如果你还不熟悉深拷贝、浅拷贝及赋值之间的关系,使用过程中会很容易掉进陷阱里,接下来详细讲解三者间的区别与其实现方式。
浅拷贝与深拷贝
- 浅拷贝是创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址(新旧对象还是共享同一块内存) ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。
- 深拷贝是将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象。
如下两张图可以清晰的看出两种拷贝方式的区别(普通对象无区别,引用类型数据内存存储不同):
一. 深拷贝
方法1:使用JSON (对象的序列化和反序列化)
//创建一个对象
var obj = {
name:'zhangsan',
age:13
}
var obj1 = JSON.parse(JSON.stringify(obj));
//因为反序列后obj1引用地址发生了改变,所以两个对象不全等
console.log(obj === obj1); //false
//改变obj1中的name属性
obj1.name = 'lisi';
//打印输出的obj的name属性没有改变
console.log(obj,obj1); // { name: 'zhangsan', age: 13 } { name: 'lisi', age: 13 }
注意 JSON.stringify()有如下局限性:
- 不支持部分类型,比如在序列化BigInt值时会报错。
- 序列化过程中会丢失部分信息,比如Date对象在序列化后会变成普通的字符串
- 不支持回环结构,序列化时会报错
- 不支持函数等序列化
方法2:第三方库lodash 的cloneDeep
Lodash 是一个一致性、模块化、高性能的 JavaScript 实用工具库。
import { cloneDeep } from 'lodash-es';
//创建一个对象
var obj = {
name:'zhangsan',
age:18
}
//使用cloneDeep方法
var obj2 = cloneDeep(obj);
//同样是改变obj2的name属性
obj2.age = 20;
//打印输出obj和obj2 发现只有obj2的name属性值改变了 原理和方法一样
console.log(obj); //{ name: 'zhangsan', age: 18 }
console.log(obj2); //{ name: 'zhangsan', age: 20 }
二. 潜拷贝
方法1.Object.assign()
Object.assign() 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象(第一个是目标对象,后面的都是源对象)。
let obj1 = { person: {name: "kobe", age: 41},sports:'basketball' };
let obj2 = Object.assign({}, obj1);
obj2.person.name = "wade";
obj2.sports = 'football'
console.log(obj1); // { person: { name: 'wade', age: 41 }, sports: 'basketball' }
// 普通数据类型不共用内存,顾值不会改变,只有引用对象的属性值变更了
方法2.函数库lodash的_.clone方法
该函数库也有提供_.clone用来做 Shallow Copy,后面我们会再介绍利用这个库实现深拷贝。
var obj1 = {
a: 1,
b: { f: { g: 1 } },
c: [1, 2, 3]
};
var obj2 = clone(obj1);
console.log(obj1.b.f === obj2.b.f);// true
方法3.展开运算符...
展开运算符是一个 es6 / es2015特性,它提供了一种非常方便的方式来执行浅拷贝,这与 Object.assign ()的功能相同。
let obj1 = { name: 'Kobe', address:{x:100,y:100}}
let obj2= {... obj1}
obj1.address.x = 200;
obj1.name = 'wade'
console.log('obj2',obj2) // obj2 { name: 'Kobe', address: { x: 200, y: 100 } }
方法4.Array.prototype.concat()
let arr = [1, 3, {
username: 'kobe'
}];
let arr2 = arr.concat();
arr2[2].username = 'wade';
console.log(arr); //[ 1, 3, { username: 'wade' } ]
方法5.Array.prototype.slice()
let arr = [1, 3, {
username: ' kobe'
}];
let arr3 = arr.slice();
arr3[2].username = 'wade'
console.log(arr); // [ 1, 3, { username: 'wade' } ]
三、赋值
当我们把一个对象赋值给一个新的变量时,赋的其实是该对象的在栈中的地址,而不是堆中的数据。也就是两个对象指向的是同一个存储空间,无论哪个对象发生改变,其实都是改变的存储空间的内容,因此,两个对象是联动的。
// 对象赋值
let obj1 = {
name : '浪里行舟',
arr : [1,[2,3],4],
};
let obj2 = obj1;
obj2.name = "阿浪";
obj2.arr[1] =[5,6,7] ;
console.log('obj1',obj1) // obj1 { name: '阿浪', arr: [ 1, [ 5, 6, 7 ], 4 ] }
console.log('obj2',obj2) // obj2 { name: '阿浪', arr: [ 1, [ 5, 6, 7 ], 4 ] }