深拷贝的简单实现以及关于取代深拷贝的想法

148 阅读2分钟

1. 深拷贝的简单实现(数组和对象)

// 使用 WeakMap 是因为其弱引用
const deepClone = (tar, map = new WeakMap()) => {
  // 判断是否为引用类型
  if (typeof tar === "object" && tar !== null || typeof tar === "function") {
    // 如果 tar 已经被拷贝过一份了, 则直接返回当时拷贝的值即可(防止循环引用)
    if (map.has(tar)) {
      return map.get(tar)
    }
    
    // 如果被克隆对象是 Array, 则返回的对象也是 Array 类型
    // 如果被克隆的对象是 Object, 则返回的对象也是 Object 类型
    const retObj = Array.isArray(tar) ? [] : {}
    
    // 如果当前是 tar 的第一次拷贝, 则记录下该次拷贝(tar 与 tar的拷贝值)
    map.set(tar, retObj)
    
    for (const key in tar) {
      // 防止拷贝原型上的属性
      if (Reflect.hasOwnProperty.call(tar, key)) {
        retObj[key] = deepClone(tar[key], map)
      }
    }
    
    return retObj
  } else {
    // 如果不是引用类型, 则直接返回值即可
    return tar
  }
}

2. 关于取代深拷贝的一个想法

什么时候需要用到深拷贝?
我能想到的场景就是: 需要修改原对象的某个属性值而又不影响到原对象的时候

关于对象的深拷贝, 其有几个问题:
函数的闭包无法拷贝怎么办? 数据之间的相互引用成环怎么办?

参照 Immutable 的想法, 我们可以简单的进行如下操作:
对 A 中的 F 进行修改, 会得到一个新的对象 a, 在此过程中, 除了与 F 有关的数据发生了变化之外, 其余的数据仍是原来的数据, 相较于深拷贝, 其防止了大量无用的操作(如果 B, D, E 中还有很深的数据嵌套, 那性能损失就很大了)

image.png

const change = (tar, {path, value}) => {
  const key = path[Symbol.iterator]()
  
  const traverse = (tar, prop) => {
    if (prop === undefined) return value 
    
    const retObj = Object.assign(Array.isArray(tar) ? [] : {}, tar)
    retObj[prop] = traverse(tar[prop], key.next().value)
    
    return retObj
  }
  
  return traverse(tar, key.next().value)
}
let obj = {b: 1, c: {e: {g: 2, h: 3}, f: 4}, d: 5}
let ret = change(obj, {path: ["c", "f"], value: 233})