手写深拷贝(上:详细版)

132 阅读3分钟

基础深拷贝--递归

function deepClone(obj){
  if(typeof obj===('object'||'function')||obj===null) return obj
  let newObj=new obj.constructor
  for(let key of obj){
      if(typeof obj ==='object'||obj!==null){
          newObj[key]=deepClone(obj[key])
      }else{
          newObj[key]=obj[key]
      }
}

未考虑到的特殊情况

1、不会被 for...in 循环遍历到的对象

for...in 循环会遍历对象自身的可枚举属性以及继承的可枚举属性

a.对象值是Symbol

因为Symbol 是一种新的原始数据类型,表示独一无二的值。每个 Symbol 值都是唯一的,它们的属性名是 Symbol 类型的,不是字符串类型的

// 处理 Symbol 作为对象属性的情况
if(typeof source === 'symbol') {
    return Symbol(source.description) 
}

b.对象值是Date或RegExp

虽然属性名是字符串类型的,但是因为这些属性是内部属性,不可枚举

if(source instanceof RegExp) return new RegExp(source) 
if(source instanceof Date) return new Date(source)

c.对象值是Map 和 Set、WeakMap 和 WeakSet

这些对象的属性名是对象类型的,不是字符串类型的。因此,它们的属性不会被 for...in 循环遍历到

针对以上情况六个引用类型,我们可以统一处理:

//直接生成一个新的实例 
let type = [Date,RegExp,Set,Map,WeakMap,WeakSet]; 
if(type.includes(obj.constructor)) return new obj.constructor(obj);

d.对象属性为Symbol

// 处理 Symbol 作为对象属性的情况
const symbolKeys = Object.getOwnPropertySymbols(obj) 
for (const symbolKey of symbolKeys) {
    newObj[Symbol(symbolKey.description)] = deepClone(obj[symbolKey])
}

终极写法

我们可以通过Reflect.ownKeys(obj)方法,它作用是获取目标对象的所有属性键,包括可枚举属性、不可枚举属性和符号类型(Symbol)的属性。它会返回一个由属性键组成的数组,数组中的顺序与属性被定义的顺序一致。于是我们有了如下写法:

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

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

3、循环引用

如果被拷贝的对象中存在循环引用,拷贝过程中会导致无限递归,从而使程序陷入死循环,最终可能导致栈溢出或程序崩溃。

例如,假设有两个对象A和B,A的属性引用了B,而B的属性引用了A,它们之间形成了循环引用。在进行深拷贝时,如果不处理循环引用,拷贝A时会拷贝B,拷贝B时会拷贝A,如此反复,导致无限循环。

一种常见的处理方式是使用一个字典或哈希表来记录已经拷贝过的对象,当遇到循环引用时,直接返回已经拷贝过的对象的引用,而不再进行拷贝操作。

这里我们用Map(该数据结构叫字典)记录已经拷贝过的对象,但由于Map会一直保留对已拷贝对象的引用(强引用),导致无法正常释放内存,故我们用WeakMap(弱引用)。

弱引用意味着当键对象没有其他引用时,它可以被垃圾回收器自动回收,即使它仍然存在于WeakMap中。

关于强弱引用:juejin.cn/post/722674…

function deepClone(obj, weakMap=new weakMap()){
if(weakMap.has(obj)) return weakMap.get(obj);
  if(typeof obj==='object'||obj===null) return obj
  let newObj=new obj.constructor
  for(let key of obj){
      if(typeof obj ==='object'||obj!==null){
          newObj[key]=deepClone(obj[key],weakMap)
      }else{
          newObj[key]=obj[key]
      }
      weakMap.set(obj,newObj);
      return newObj
}

最终写法

const deepClone = function (obj, weakMap = new WeakMap()) {
    if (weakMap.has(obj)) return weakMap.get(obj);
    if (typeof obj === 'function') return obj.bind({});
    if (Object.prototype.toString.call(obj) === '[object Symbol]') return Object(Symbol.prototype.valueOf.call(obj));

    let newObj;
    //obj === Object(obj)也可以这么判断
    if (typeof obj === 'object' && obj !== null) {
        if (obj instanceof Date) {
            newObj = new Date(obj);
        } else if (obj instanceof RegExp) {
            newObj = new RegExp(obj);
        } else if (obj instanceof Set) {
            newObj = new Set(Array.from(obj, item => deepClone(item, weakMap)));
        } else if (obj instanceof Map) {
            newObj = new Map(Array.from(obj, ([key, value]) => [deepClone(key, weakMap), deepClone(value, weakMap)]));
        } else if (obj instanceof WeakMap || obj instanceof WeakSet) {
            throw new Error('Cannot clone weak collections');
        } else {
            newObj = Object.create(Object.getPrototypeOf(obj));
        }

        weakMap.set(obj, newObj);

        // 处理属性
        let keys = Object.getOwnPropertyNames(obj);
        let symbols = Object.getOwnPropertySymbols(obj);
        let descriptors = Object.getOwnPropertyDescriptors(obj);

        for (let key of keys) {
            if (obj.hasOwnProperty(key)) {
                descriptors[key].value = isComplexDataType(obj[key]) ? deepClone(obj[key], weakMap) : obj[key];
            }
        }

        for (let symbol of symbols) {
            descriptors[symbol].value = isComplexDataType(obj[symbol]) ? deepClone(obj[symbol], weakMap) : obj[symbol];
        }

        Object.defineProperties(newObj, descriptors);
    } else {
        newObj = obj;
    }

    return newObj;
};