深拷贝? 浅拷贝?

138 阅读3分钟

1. 区别

对于原始值,两者都是直接拷贝一份,且修改拷贝后的值不会影响原来的;对于引用值,浅拷贝只复制指针,当两个指针指向同一个地址时,共享一块内存,修改值会互相影响,而深拷贝会开辟新的内存空间,即修改其中一个的值,不会影响到另外一个值。

2. 如何实现浅拷贝?

  1. 遍历赋值

    function clone(obj) {
        // 只拷贝对象类型
        if (typeof obj !== 'object' || obj == null) return obj;
        // 根据obj的类型判断是新建一个数组还是对象
        var newObj = Array.isArray(obj) ? [] : {};
        // 遍历obj
        for (var key in obj) {
           	//判断是obj的属性才拷贝,因为for in会遍历对象原型链上的属性
            if (obj.hasOwnProperty(key)) {
                newObj[key] = obj[key];
            }
        }
        return newObj;
    }
    
  2. ES6扩展运算符

    var obj = { a:1, arr: [2,3] };
    var obj1 = {...obj}
    
  3. 数组的方法(拷贝数组)

    1. Array.from()
    2. Array.prototype.concat()
    3. Array.prototype.slice()
  4. ES6 Obect.assign()

    var obj = { a:1, arr: [2,3] };
    var obj1 = Object.assign({}, obj);
    obj.arr[1] = 5;
    
    console.log(obj1.arr[1]);//输出结果:5。
    

3. 如何实现深拷贝

  1. 借助JSON

    var obj = { a:1, arr: [2,3] };
    var obj1 = JSON.parse(JSON.stringify(obj));
    obj.arr[1] = 5;
    
    console.log(obj1.arr[1]); //输出结果:3。
    

    注意:这个方法有局限性,会忽略undefined、symbol类型的数据,只能处理能够被json直接表示的数据结构;也不能解决循环引用的对象。

  2. JQuery的extend

    jQuery.extend( [deep], target, object1 [, objectN ] )

    函数的第一个参数可以传一个布尔值, 如果为 true,我们就会进行深拷贝, false 依然当做浅拷贝,这个时候,target 就往后移动到第二个参数。

    var obj1 = {
        a: 1,
        b: { b1: 1, b2: 2 }
    };
    
    var obj2 = {
        b: { b1: 3, b3: 4 },
        c: 3
    };
    
    var obj3 = {
        d: 4
    }
    
    console.log($.extend(obj1, obj2, obj3));
    
    // {
    //    a: 1,
    //    b: { b1: 3, b3: 4 },
    //    c: 3,
    //    d: 4
    // }
    以上为浅拷贝。
    
  3. loadash

    lodash —— _.clone() / _.cloneDeep()
    在lodash中关于复制的方法有两个,分别是_.clone()和_.cloneDeep()。
    其中_.clone(obj, true)等价于_.cloneDeep(obj)。
    
  4. 浅拷贝+递归

    当遇到子成员是引用对象时,使用递归层层复制。

    function deepClone(obj) {
      let type = typeof obj
      //如果是基本数据类型直接返回
      if (type !== 'object' || obj === null) return obj
      var newObj = Array.isArray(obj) ? [] : {}
      if (obj && typeof obj === 'object') {
        for (var k in obj) {
          if (obj.hasOwnProperty(k)) {
            newObj[k] = obj[k] && typeof obj[k] === 'object' ? deepClone(obj[k]) : obj[k]
          }
        }
      }
      return newObj
    }
    

但是这种深拷贝并不能解决循环引用的问题。

4. 解决循环引用

假如拷贝的对象中存在循环引用关系,这样在递归中会导致死循环

  1. 利用数组去记录每个引用对象,看它是否已经拷贝过

    //判断是否是一个对象
    function isObject(obj) {
     return typeof obj === 'object' && obj != null
    }
    function find(arr, item) {
     for (var i = 0; i < arr.length; i++) {
         //判断item是否有被拷贝过
       if (arr[i].source === item) {
         return arr[i]
       }
     }
     return null
    }
    function deepClone(source, list) {
     if (!isObject(source)) return source
     //用一个数组来记录被拷贝过的引用对象
     if (!list) list = [];
     var target = Array.isArray(source) ? [] : {}
     var data = find(list, source)
     if (data) {
       //如果有被拷贝过,直接返回拷贝的结果,不需要再递归(会造成死循环)
       return data.target
     }
     list.push({
       source, target
     })
     for (var key in source) {
       if (Object.prototype.hasOwnProperty.call(source, key)) {
         if (isObject(source[key])) {
           //递归调用,注意需要将list传进去,记录所有被拷贝过的引用对象
           target[key] = deepClone(source[key], list)
         } else {
           target[key] = source[key]
         }
       }
     }
     return target
    }
    
  2. 也可以用一个WeakMap结构去记录对象的引用关系

    function deepCopy(obj) {
      // 记录所有的对象的引用关系
      let map = new WeakMap();
    
      function dp(obj) {
        let result = null;
        let keys = Object.keys(obj);
        let key = null,
          temp = null,
          existobj = null;
    
        existobj = map.get(obj);
        //如果这个对象已经被记录则直接返回原有的拷贝的结果
        if (existobj) {
          return existobj;
        }
    
        result = Array.isArray(obj) ? [] : {}
        map.set(obj, result);
    
        for (let i = 0, len = keys.length; i < len; i++) {
          key = keys[i];
          temp = obj[key];
          if (temp && typeof temp === 'object') {
            result[key] = dp(temp);
          } else {
            result[key] = temp;
          }
        }
        return result;
      }
      return dp(obj);
    }