js deep clone 深克隆

7,309 阅读2分钟

首先,值的拷贝,通常有三种方式,由于基本类型与引用类型在内存中存储位置和存储方式的不同,导致了以下三种概念的衍生:

  • = 赋值:多个指针指向的是同一个堆中的地址,所以相互有影响;
  • 浅拷贝:在堆中重新创建内存,拷贝前后基本数据类型不受影响,但只拷贝一层,无法拷贝子对象;所以改变浅拷贝得到的对象中的引用类型时,原始数据会受到影响;例如数组的concat和slice方法;
  • 深拷贝:对子对象也可以拷贝,拷贝前后两个对象互不影响,是对对象以及对象的所有子对象进行拷贝;思路就是递归调用浅拷贝的逻辑,把所有属于对象的属性类型都遍历赋给另一个对象即可。完全的拷贝一个对象,即使嵌套了对象,两者也相互分离,修改一个对象的属性,也不会影响另一个。


看下数组结构的拷贝:

var new_arr = JSON.parse( JSON.stringify(arr) );

但此法无法拷贝函数。concat、slice、JSON.stringify 都算是技巧类,可以根据实际情况适当使用。

初步实现一个浅拷贝:

在看开源项目的过程中,经常会看到类似如下的源码。for...in循环对象的所有枚举属性,然后再使用hasOwnProperty()方法来忽略继承属性。

const shallowClone = (obj) => {  
    if (typeof obj !== 'object') return
    let newObj = obj instanceof Array ? [] : {}  
    for (let key in obj) {    
        if (obj.hasOwnProperty(key)) {      
            newObj[key] = obj[key]    
        }  
     }
     return newObj
}

初步实现一个深拷贝:

const deepClone = (obj) => {  
    if (typeof obj !== 'object') return
    let newObj = obj instanceof Array ? [] : {}  
    for (let key in obj) {    
       if (typeof obj[key] === 'object') {      
         newObj[key] = deepClone(obj[key])    
       } else {      
          newObj[key] = obj[key]    
       }  
     }
     return newObj
}

深拷贝会完全的克隆一个新对象,但因为使用递归,性能会不如浅拷贝,在开发中,还是要根据实际情况进行选择。


第三方库的实现

Underscore _.clone()

实际上是一种浅复制 (shallow-copy),所有嵌套的对象和数组都是直接复制引用而并没有进行深复制。源码:

// Create a (shallow-cloned) duplicate of an object.
_.clone = function(obj) {
  if (!_.isObject(obj)) return obj;
  return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
};

jQuery $.extend()

var x = {
    a: 1,
    b: { f: { g: 1 } },
    c: [ 1, 2, 3 ]
};

var y = $.extend({}, x),          //shallow copy
    z = $.extend(true, {}, x);    //deep copy

y.b.f === x.b.f       // true
z.b.f === x.b.f       // false

lodash  _.clone() / _.cloneDeep()

在lodash中关于复制的方法有两个,分别是_.clone()_.cloneDeep()。其中_.clone(obj, true)等价于_.cloneDeep(obj)

jQuery 无法正确深复制 JSON 对象以外的对象,而 lodash 花了大量的代码来实现 ES6 引入的大量新的标准对象。lodash 针对存在环的对象的处理也是非常出色的。因此相较而言,lodash 在深复制上的行为反馈比前两个库好很多。


参考源:

https://juejin.cn/post/6844903493925371917

https://segmentfault.com/a/1190000002801042

https://github.com/mqyqingfeng/Blog/issues/32