JavaScript中的深拷贝和浅拷贝问题
1.概念
深拷贝实质上是拷贝要拷贝的对象自身,浅拷贝实质上是对要拷贝对象的引用。
- 当你浅拷贝复制A为B的时候,当数据类型复杂的时候,改动B会给A造成影响;
- 当你深拷贝复制A为B的时候,任何时候,改动B都不会给A造成影响。如果只改动简单数据类型后,改动B会给A造成影响,那这个操作就不是拷贝。
2.直接用等号赋值不是拷贝
在复杂数据类型中,改动B会给A造成影响:
let obj1 = {
name: "bob",
};
let obj2 = obj1;
obj2.name = "mike";
obj2.age = 23;
console.log("obj1", obj1);
console.log("obj2", obj2);
如果是简单的数据类型,相当于之前给之前的赋值替掉了,所以不会造成影响:
let obj1 = "bob";
let obj2 = obj1;
obj2 = "mike";
console.log("obj1", obj1);
console.log("obj2", obj2);
3.浅拷贝
注意:对于对象的浅拷贝,不能复制原型链上的属性和方法。
3.1 扩展运算符
数组(es6中浅拷贝):
let a = [
1,
2,
3,
{
name: "bob",
age: 12,
},
];
b = [...a];
b[2] = 100;
b[3].age = 15;
console.log("a", a);
console.log("b", b);
对于对象,需要该对象拥有iterater接口。这个之间讲过。
3.2 Object.assign()
可以拷贝对象。
let person1 = {
name: "bob",
age: 12,
arr: [1, 2, 3],
};
let person2 = {};
person2 = Object.assign(person2, person1);
person2.age = 19;
person2.arr[1] = 10000;
console.log("person1", person1);
console.log("person2", person2);
3.3 concat()
let arr1 = [
1,
2,
3,
{
name: "bob",
age: 18,
},
];
let arr2 = [];
arr2 = arr2.concat(arr1);
arr2[1] = 1000;
arr2[3].age = 99;
console.log(arr1);
console.log(arr2);
3.4 Array.prototype.slice()
const arr = [1, 2, { foo: "foo" }];
const _arr = arr.slice();
_arr[1] = 9999;
_arr[2].foo = "bar";
console.log(arr);
console.log(_arr);
3.5 手动实现浅拷贝
//for...in循环遍历对象自身的和继承的可枚举属性(不含Symbol属性)。
function shallowClone(obj) {
if (obj === null || obj === undefined) {
return obj;
}
// 数据是基本型数据,直接返回
if (typeof obj !== "object") return obj;
// 对象为空,直接返回
//Object.keys返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)的键名。
if (!Object.keys(obj).length) return obj;
// 据obj的类型判断是新建数组还是对象
let newClone = Array.isArray(obj) ? [] : {};
//for...in循环:只遍历对象自身的和继承的可枚举的属性。
for (let key in obj) {
//判断当前对象/数组是否有自身的属性 不包括继承
// 核心
if (obj.hasOwnProperty(key)) {//确保不拷贝原型链上的属性
// 赋值给新对象/数组
newClone[key] = obj[key];
}
}
return newClone;
}
测试一下:
let person1 = {
name: "bob",
age: 12,
arr: [1, 2, 3],
};
let person2 = {};
person2 = shallowClone(person1);
person2.age = 19;
person2.arr[1] = 10000;
console.log("person1", person1);
console.log("person2", person2);
const arr = [1, 2, { foo: "foo" }];
const _arr = shallowClone(arr);
_arr[1] = 9999;
_arr[2].foo = "bar";
console.log(arr);
console.log(_arr);
const str1 = "123";
const str2 = shallowClone(str1);
str2 = "111";
console.log("str1 : ", str1);
console.log("str2 : ", str2);
4.深拷贝
4.1 使用JSON.parse(),JSON.stringify()
这个方法有个问题,就是无法处理函数和正则,函数会丢失,正则会转换为 null 对象。
const obj = {
name: "bob",
age: 12,
arr: [1, 2, 3],
};
const _obj = JSON.parse(JSON.stringify(obj));
_obj.arr[2] = 999;
console.log(obj);
console.log(_obj);
4.2 lodash库
lodash是一个JS库,我们可以使用lodash中的cloneDeep实现深拷贝。
我这里用node安装了lodash,然后把lodash文件夹中的lodash.min.js拿出来。
npm init -y
npm install lodash
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script src="./lodash.min.js"></script>
</head>
<body>
<script>
const obj = {
name: "bob",
age: 12,
arr: [1, 2, 3],
};
const _obj = _.cloneDeep(obj);
_obj.arr[2] = 999;
console.log(obj);
console.log(_obj);
</script>
</body>
</html>
4.3 递归函数
// 深拷贝:采用循环递归方式
function deepClone(obj, wm = new WeakMap()) {
// 对数据预处理
if(obj === undefined) return undefined
if(obj === null) return null
if(obj instanceof Date) return new Date(obj)
if(obj instanceof RegExp) return new RegExp(obj)
if(obj instanceof Error) return new Error(obj.message)
// 中断条件
// 若数据是基本类型,则直接返回不拷贝
// 对象为空,则直接返回不拷贝
if(typeof obj !== 'object') return obj
if(!Object.keys(obj).length) return obj
// 若WeakMap已存在指定键的元素,则直接存储返回元素,不必重新建立弱引用,节约空间
if(wm.has(obj)) return wm.get(obj)
let newClone = Array.isArray(obj) ? [] : {};
wm.set(obj, newClone)
for(let key in obj) {
if(obj.hasOwnProperty(key)) {
// 核心
newClone[key] = deepClone(obj[key], wm)
}
}
return newClone
}
测试一次:
const obj = {
name: "bob",
age: 12,
arr: [1, 2, 3],
};
const _obj = deepClone(obj);
_obj.arr[2] = 999;
console.log(obj);
console.log(_obj);
let str1 = 12;
let str2 = deepClone(str1);
str2 = 99;
console.log(str1);
console.log(str2);
5.总结
浅拷贝:
- 扩展运算符
- Object.assign
- slice
- concat
- 手写
深拷贝:
- JSON.parse(),JSON.stringify()
- lodash库
- 递归实现