js实现深拷贝

JS中拷贝对象可以按照拷贝的程度可以分为浅拷贝和深拷贝,有些时候我们需要拷贝之后的对象和拷贝之前的对象解耦,即脱离联系,也就是改变其中一者,另一者不会变化,典型的场景有:状态的回溯。如果我们对状态对象使用浅拷贝,则无法对状态进行回溯,但如果使用深拷贝,则可以很容易的对状态进行回溯和跟踪。

实现深拷贝,主要有以下两种方式:(值得一提的是,JS原生数组中的 concat、slice 方法还有 Object.assign 方法都是一层拷贝,即浅拷贝)

1、JSON 法

function copy(o) {
    return JSON.parse(JSON.stringify(o))
}

注:该方法无法拷贝函数,如果对象中有值是函数,函数被不会正确拷贝。

2、递归实现

var deepCopy = function(obj) {
    if (typeof obj !== 'object') return obj
    var newObj = (Object.prototype.toString.call(obj) === '[object Array]') ? [] : {}
    for (var key in obj) {
        if (obj.hasOwnProperty(key)) {
            newObj[key] = (typeof obj[key] !== 'object') ? obj[key]: deepCopy(obj[key])
        }
    }
    return newObj
}

上面的方法只能实现{} []或者子元素是{} []的深拷贝,如果有一下要求怎么实现
要求:

支持对象、数组、日期、正则的拷贝。
处理原始类型(原始类型直接返回,只有引用类型才有深拷贝这个概念)。
处理 Symbol 作为键名的情况。
处理函数(函数直接返回,拷贝函数没有意义,两个对象使用内存中同一个地址的函数,问题不大)。
处理 DOM 元素(DOM 元素直接返回,拷贝 DOM 元素没有意义,都是指向页面中同一个)。
额外开辟一个储存空间 WeakMap,解决循环引用递归爆栈问题(引入 WeakMap 的另一个意义,配合垃圾回收机制,防止内存泄漏)。

答案:

function deepClone (target, hash = new WeakMap()) { // 额外开辟一个存储空间WeakMap来存储当前对象
  if (target === null) return target // 如果是 null 就不进行拷贝操作
  if (target instanceof Date) return new Date(target) // 处理日期
  if (target instanceof RegExp) return new RegExp(target) // 处理正则
  if (target instanceof HTMLElement) return target // 处理 DOM元素

  if (typeof target !== 'object') return target // 处理原始类型和函数 不需要深拷贝,直接返回

  // 是引用类型的话就要进行深拷贝
  if (hash.get(target)) return hash.get(target) // 当需要拷贝当前对象时,先去存储空间中找,如果有的话直接返回
  const cloneTarget = new target.constructor() // 创建一个新的克隆对象或克隆数组
  hash.set(target, cloneTarget) // 如果存储空间中没有就存进 hash 里

  Reflect.ownKeys(target).forEach(key => { // 引入 Reflect.ownKeys,处理 Symbol 作为键名的情况
    cloneTarget[key] = deepClone(target[key], hash) // 递归拷贝每一层
  })
  return cloneTarget // 返回克隆的对象
}

测试一下:

const obj = {
  a: true,
  b: 100,
  c: 'str',
  d: undefined,
  e: null,
  f: Symbol('f'),
  g: {
    g1: {} // 深层对象
  },
  h: [], // 数组
  i: new Date(), // Date
  j: /abc/, // 正则
  k: function () {}, // 函数
  l: [document.getElementById('foo')] // 引入 WeakMap 的意义,处理可能被清除的 DOM 元素
}

obj.obj = obj // 循环引用

const name = Symbol('name')
obj[name] = 'lin'  // Symbol 作为键

const newObj = deepClone(obj)

console.log(newObj)

参考:
轻松拿下 JS 浅拷贝、深拷贝

你可能感兴趣的:(js实现深拷贝)