- 浅拷贝只是拷贝一层, 更深层次对象级别的只拷贝引用。
- 深拷贝拷贝多层, 每一级别的数据都会拷贝。
- 深浅拷贝的不同是针对引用数据类型而言的。
由于引用数据的变量值只是一个指向该引用数据的指针(地址)。所以对于引用数据来说,浅拷贝只复制了这个指针(地址)值,而没有复制对象本身,新旧对象还是共享同一块内存,因此同新对象改变属性值会对原对象造成影响。但深拷贝会另外创造一个一模一样的对象,把每一层的数据都拷贝过去,新对象跟原对象不共享内存,修改新对象不会改到原对象。
引用类型一般是数组和对象,会出现浅拷贝问题,因此分为两个部分。
一、对象浅拷贝的解决方案
1. JSON转换方法
JSON.stringify(obj)
将一个 JavaScript 对象或值转换为 JSON 字符串,称之为 JSON 序列化JSON.parse(str)
解析JSON字符串,构造由字符串描述的JavaScript值或对象,称之为 JSON 反序列化
首先将对象转为JSON字符串,这样它里面的所有内容都包含在这个字符串中,变成了一个普通数据类型,再将这个字符串转回对象类型,赋值给新的变量,这时候是一个根据字符串解析得到的新对象,与原对象不一样,也就是变量中存的是一个新的地址,完成深拷贝。
const obj1 = {
a: 1,
b: undefined,
arr: [1, 2, 3],
fun: () => {}
}
let obj2 = JSON.parse(JSON.stringify(obj1));
obj2.a = 2;
obj2.arr[0] = 4;
console.log(obj1);
console.log(obj2);
【缺点】数据类型为function和数据值为undefined的属性不会被拷贝过来。
【应用场景】当对象中没有function和undefined数据时,这种方法比较简洁也能达到深拷贝的效果。
2. Object.assign()
ES6 新增的浅拷贝方法:Object.assign(target, ...sources)
,用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。如果出现重复属性,则后面的属性值覆盖前面的属性值。
const obj1 = {
a: 1,
b: undefined,
arr: [1, 2, 3],
fun: () => {}
}
let obj2 = Object.assign({}, obj1);
obj2.a = 2;
obj2.arr[0] = 4;
console.log(obj1);
console.log(obj2);
【特点】所有数据类型均可拷贝,但是只拷贝了一级属性,如果属性中存在引用数据类型,还是无法完全拷贝。
【应用场景】当对象中数据编辑简单,没有二级属性时,可以使用该方法拷贝。
3. 扩展运算符
ES6中对象的扩展运算符(…)用于取出参数对象的所有可遍历属性,拷贝到当前对象之中。
const obj1 = {
a: 1,
b: undefined,
arr: [1, 2, 3],
fun: () => {}
}
let obj2 = {...obj1};
obj2.a = 2;
obj2.arr[0] = 4;
console.log(obj1);
console.log(obj2);
特点和应用场景与上一种方法一样。
4. 递归
function deepClone(data) {
//首先判断要拷贝的值是什么类型,数组or对象
const newdata = Array.isArray(data) ? [] : {};
// 如果data是对象,k表示属性名
// 如果data是数组,k表示索引,数组可以看成是一个对象,属性值就是索引值
for (let k in data) {
// 数组和对象的typeof值都是object
// 如果data[k]的typeof值是object,说明这个属性值是引用类型数据,需要做深拷贝
if (data[k] && typeof data[k] === 'object') {
newdata[k] = deepClone(data[k]);
} else {
newdata[k] = data[k];
}
}
return newdata;
}
const obj1 = {
a: 1,
b: undefined,
arr: [1, 2, 3],
fun: () => {}
}
let obj2 = deepClone(obj1);
obj2.a = 2;
obj2.arr[0] = 4;
console.log(obj1);
console.log(obj2);
递归是一种比较完美的解决方案,可以完全实现深拷贝,但是实现方法复杂。所以一般先进行数据类型的判断,如果数据类型没有太复杂,可以优先选择上面三种方法。
二、数组浅拷贝的解决方案
数组浅拷贝和对象浅拷贝的解决方案基本类似。
1. JSON转换方法
与对象的解决方案类似,先将对象转为JSON字符串,再将这个字符串转回对象类型,赋值给新的变量,就可以实现深拷贝。
const arr1 = [1, 2, 3, undefined, { a: 1, b: undefined, fun: () => {} }, () => {}];
let arr2 = JSON.parse(JSON.stringify(arr1));
arr2[0] = 2;
arr2[4].a = 2;
console.log(arr1);
console.log(arr2);
同样拷贝特点也一样,数据类型为function和数据值为undefined的属性不会被拷贝过来或者拷贝出错。
2. Object.assign()
const arr1 = [1, 2, 3, undefined, { a: 1, b: undefined, fun: () => {} }, () => {}];
let arr2 = Object.assign([], arr1);
arr2[0] = 2;
arr2[4].a = 2;
console.log(arr1);
console.log(arr2);
【特点】所有数据类型均可拷贝,但是只拷贝了一级属性,如果属性中存在引用数据类型,还是无法完全拷贝。
3. 扩展运算符
实现效果与上一种方法完全相同。这里只给出代码,不展示效果了。
const arr1 = [1, 2, 3, undefined, { a: 1, b: undefined, fun: () => {} }, () => {}];
let arr2 = [...arr1];
arr2[0] = 2;
arr2[4].a = 2;
console.log(arr1);
console.log(arr2);
4. slice(),concat()
数组的slice()方法以新的数组对象,返回数组中被选中的元素,且不会改变原数组。
数组的concat()方法方法用于连接两个或多个数组,不会更改原数组,而是返回一个新数组,其中包含已连接数组的值。
实现效果与上两种方法相同。这里只给出代码,不展示效果了。
const arr1 = [1, 2, 3, undefined, { a: 1, b: undefined, fun: () => {} }, () => {}];
let arr2 = arr.slice();
// let arr2 = [];
// let arr2 = arr2.concat(arr1);
arr2[0] = 2;
arr2[4].a = 2;
console.log(arr1);
console.log(arr2);
5. 递归
比较完美的解决方案,可直接调用上面封装的deepClone函数。不再详细描述。
三、总结
引用数据类型的浅拷贝解决方案主要包括:
- JSON转换。先使用
JSON.stringify(data)
将数转换为JSON字符串,再使用JSON.parse(str)
将字符串转回对象。这种方法可以实现深拷贝,但不会拷贝function和undefined数据。 - Object.assign(data)。所有类型的数据都可以拷贝,但是只能拷贝一级属性,不能拷贝二级及以上的属性。
- 扩展运算符。所有类型的数据都可以拷贝,但是只能拷贝一级属性,不能拷贝二级及以上的属性。
- slice(),concat()。数组专属。所有类型的数据都可以拷贝,但是只能拷贝一级属性,不能拷贝二级及以上的属性。
- 递归。较为完美的解决方案。但实现起来较为复杂。
在使用过程中,应当结合数据的情况选用具体的解决方案,如果数据中不存在undefined,function类型的属性值,优先选择1;如果数据只有一级属性,则优先选择2-4;如果数据类型很复杂,这时候考虑使用5。