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] }
posted @ 2017-12-21 10:39  Mr.曹  阅读(436)  评论(0编辑  收藏  举报