# 对象深拷贝

# 什么是对象拷贝

你可以理解为复制一个对象,但在程序中复制对象总是困难重重

一个复制失败的例子

var a = {
  o: {
    name: 1,
    data: {
      c: 2
    }
  }
}
var b = {
  ...a
}
console.log(b.o.data === a.o.data) // true

以上代码我们并没有完全复制对象 a 因为 a.data 和 b.data 还是引用关系。

# 什么是浅拷贝

上面的代码虽然没有复制成功,但是已经完成了浅拷贝。浅拷贝的意思是只要完成最外层的拷贝即可。

# 对象深拷贝的一些思考

如果让你实现深拷贝,也行你会写成这样

var a = {
  o: {
    name: 1,
    data: {
      c: 2
    }
  }
}
var b = {
  ...a,
  o: {
    ...a.o,
    data: {
      ...a.o.data
    }
  }
}
console.log(b.o.data === a.o.data) // false

看起来我们这次已经实现了对象的深拷贝,但这是完全手动实现的。这样做并没有意义。

lodahs 库已经实现了 cloneDeep 函数,如果我们在工作中建议使用这种方式实现深拷贝

import _ from 'lodash'
var a = {
  //...
}
var b = _.cloneDeep(a);
console.log(a === b) // false

虽然已经有现车的库可以帮我们解决问题,但我们还是要明白其中的原理。要知道想要深拷贝一个对象到底都经历了什么

  • 对象的原型链
  • null, undefined, Date, Symbol 类型的数据如何拷贝

# 具体实现

const isComplexDataType = obj => (typeof obj === 'object' || typeof obj === 'function') && (obj !== null)

const deepClone = function (obj, hash = new WeakMap()) {

    if (obj.constructor === Date) return new Date(obj);   //日期对象就返回一个新的日期对象
    if (obj.constructor === RegExp) return new RegExp(obj);  //正则对象就返回一个新的正则对象

    //如果成环了,参数obj = obj.loop = 最初的obj 会在WeakMap中找到第一次放入的obj提前返回第一次放入WeakMap的cloneObj
    if (hash.has(obj)) return hash.get(obj)

    let allDesc = Object.getOwnPropertyDescriptors(obj);     //遍历传入参数所有键的特性
    let cloneObj = Object.create(Object.getPrototypeOf(obj), allDesc); //继承原型链

    hash.set(obj, cloneObj)

    for (let key of Reflect.ownKeys(obj)) {   //Reflect.ownKeys(obj)可以拷贝不可枚举属性和符号类型
        // 如果值是引用类型(非函数)则递归调用deepClone
        cloneObj[key] =
            (isComplexDataType(obj[key]) && typeof obj[key] !== 'function') ?
                deepClone(obj[key], hash) : obj[key];
    }
    return cloneObj;
};

let obj = {
    num: 0,
    str: '',
    boolean: true,
    unf: undefined,
    nul: null,
    obj: {
        name: '我是一个对象',
        id: 1
    },
    arr: [0, 1, 2],
    func: function () {
        console.log('我是一个函数')
    },
    date: new Date(0),
    reg: new RegExp('/我是一个正则/ig'),
    [Symbol('1')]: 1,
};

Object.defineProperty(obj, 'innumerable', {
    enumerable: false,
    value: '不可枚举属性'
});

obj = Object.create(obj, Object.getOwnPropertyDescriptors(obj))

obj.loop = obj

let cloneObj = deepClone(obj);

console.log('obj', obj);
console.log('cloneObj', cloneObj);

for (let key of Object.keys(cloneObj)) {
    if (typeof cloneObj[key] === 'object' || typeof cloneObj[key] === 'function') {
        console.log(`${key}相同吗? `, cloneObj[key] === obj[key])
    }
}

# 参考

上次更新: 10/20/2020, 2:37:44 AM