js之浅拷贝与深拷贝,并处理循环引用的问题
- 浅拷贝:只会复制对象的第一层数据
- 深拷贝:不仅仅会复制第一层的数据,如果里面还有对象,会继续进行复制,直到复制到全是基本数据类型为止
简单来说,浅拷贝是都指向同一块内存区块,而深拷贝则是另外开辟了一块区域
例如,下面就是浅拷贝:
let arr = [1,2,3,4];
let arr2 = arr;
arr2.push(5);
console.log(arr); // [1, 2, 3, 4, 5]
console.log(arr2); // [1, 2, 3, 4, 5]
对深拷贝来说,有以下的方法:
1. 深拷贝的简单方法:
- 对数组来说:
let arr = [1,2,3,4];
let arr2 = [];
for(let i=0;i<arr.length;i++){
arr2[i] = arr[i];
}
arr2.push(5);
console.log(arr); // [1, 2, 3, 4]
console.log(arr2); // [1, 2, 3, 4, 5]
- 对对象来说(for..in):
let obj = {
name:"haha",
age:18
}
let obj2 = {};
for(var attr in obj){
obj2[attr] = obj[attr]
}
obj2.name = 'hehe';
console.log(obj); // {name: "haha", age: 18}
console.log(obj2); // {name: "hehe", age: 18}
2. 转成 JSON 再转回来
用JSON.stringify把对象转成字符串,再用JSON.parse把字符串转成新的对象。
let arr = [1,2,3,4];
let arr2 = JSON.parse(JSON.stringify(arr));
// JSON.parse() 用于将一个 JSON 字符串转换为 JavaScript 对象。
// JSON.stringify() 用于将 JavaScript 值转换为 JSON 字符串。
arr2.push(5);
console.log(arr); // [1, 2, 3, 4, 5]
console.log(arr2); // [1, 2, 3, 4, 5]
注意,该方法缺点
1.如果obj里面有时间对象,则时间将只是字符串的形式,而不是对象的形式;
2.如果obj里有RegExp(正则表达式的缩写)、Error对象,则序列化的结果将只得到空对象;;
3. 如果obj里有函数,undefined,则序列化的结果会把函数或 undefined丢失;
4. 如果obj里有NaN、Infinity和-Infinity,则序列化的结果会变成null;
5. JSON.stringify()只能序列化对象的可枚举的自有属性,例如如果obj中的对象是有构造函数生成的,则使用JSON.parse(JSON.stringify(obj))深拷贝后,会丢弃对象的constructor;
6. 如果对象中存在循环引用的情况也无法正确实现深拷贝;
3. 深拷贝的es6方法:Object.assign()
let obj = {
name:"haha",
age:18
}
let obj2 = {};
Object.assign(obj2,obj);
obj2.name = 'hehe';
console.log(obj); // {name: "haha", age: 18}
console.log(obj2); // {name: "hehe", age: 18}
4. 深拷贝的方法封装:
但是,对于下面的例子(包含多层对象),不能用Object.assign()
let arr = [1,2,3,4,[5],{}];
let arr2 = Object.assign([],arr);
arr2[4].push(6);
console.log(arr) // [1, 2, 3, 4, [5,6], {…}]
console.log(arr2) // [1, 2, 3, 4, [5,6], {…}]
所以,这里封装了一个深拷贝函数deepClone
function deepClone(obj){ //深度克隆
let o = obj.push?[]:{};
for(let key in obj){
//值为复合类型
if(typeof obj[key] === 'object' && obj[key]!=null){
o[key] = deepClone(obj[key]);
}else{
o[key] = obj[key];
}
}
return o;
}
let arr = [1,2,3,4,[5],{}];
let arr2 = deepClone(arr);
arr2[4].push(6);
console.log(arr) // [1, 2, 3, 4, [5], {…}]
console.log(arr2) // [1, 2, 3, 4, [5,6], {…}]
👉 但是
对于循环引用的对象使用deepClone函数的深拷贝很明显会直接栈溢出。例如下面的对象:
let a = { name: 'cc', }
a.a = a
所以,下面使用的map
来解决循环引用的问题:
function deepClone(obj, m = new Map()) {
if (m.has(obj)) {
return m.get(obj)
}
let copyObj = obj.push ? [] : {}
m.set(obj, copyObj)
for (let key in obj) {
if (typeof obj[key] === 'object' && obj[key] !== null) {
copyObj[key] = deepClone(obj[key], m)
} else {
copyObj[key] = obj[key]
}
}
return copyObj
}
let a = { name: 'cc', }
a.a = a
let b = deepClone(a)
b.name = 'bb'
console.log(a) // { name: 'cc', a: [Circular] }
console.log(b) // { name: 'bb', a: [Circular] }