Skip to content
大纲

深浅拷贝区别与实现

建议分析 lodash 的深拷贝实现

浅拷贝

只考虑对象类型

js
// 1
Object.assign({}, obj)

// 2
{...obj}

简单实现

js
function shallowCopy(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
}

深拷贝 deepclone

递归实现

深度优先

  1. 判断类型,正则日期直接返回新对象
  2. 是函数,通过 new FunctiontoString 返回 clone
  3. 空 null或者非对象类型,直接返回原值
  4. 考虑循环引用,判断如果hash中含有直接返回hash中的值
  5. 新建一个相应的 new obj.constructor 加入 hash
  6. 遍历对象递归(普通 keykeysymbol 类型的情况)
  7. key 是 symbol 类型,类似 6,也是遍历递归

深度遍历广度遍历的区别?

对于算法来说 无非就是时间换空间 空间换时间

  1. 深度优先不需要记住所有的节点,所以占用空间小,而广度优先需要先记录所有的节点占用空间大
  2. 深度优先有回溯的操作 (没有路走了需要回头) 所以相对而言时间会长一点
  3. 深度优先采用的是堆栈的形式,即先进后出
  4. 广度优先则采用的是队列的形式,即先进先出
点我查看详细
js
// 递归实现深拷贝 深度优先
function deepClone(obj, hash = new WeakMap()) {
  if (obj instanceof RegExp) return new RegExp(obj)
  if (obj instanceof Date) return new Date(obj)
  if (typeof obj === 'function')
    return new Function('return ' + obj.toString())()

  // 其他基本类型,直接返回
  if (obj === null || typeof obj !== 'object') return obj

  // 处理循环引用的情况
  if (hash.has(obj)) {
    return hash.get(obj)
  }
  // new 一个相应的对象
  // obj 为 Array,相当于 new Array()
  // obj 为 Object,相当于 new Object()
  // 找到的是所属类原型上的 constructor,而原型上的 constructor 指向的是当前类本身
  let constr = new obj.constructor() // 为什么用这个,完成原型链的 clone,是否可以用 Object.create(obj)?TODO:
  hash.set(obj, constr)

  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      // 递归拷贝
      constr[key] = deepClone(obj[key], hash)
    }
  }
  // 考虑 key 是 symbol 类型的情况
  let symbolObj = Object.getOwnPropertySymbols(obj)
  for (let i = 0; i < symbolObj.length; i++) {
    if (obj.hasOwnProperty(symbolObj[i])) {
      constr[symbolObj[i]] = deepClone(obj[symbolObj[i]], hash)
    }
  }
  return constr
}

// testing
let symbol1 = Symbol(1)
const obj1 = {
  a: { b: 1 },
  c: [{ d: 1 }],
  e: null,
  f: undefined,
  [symbol1]: { a: 1 },
  g: symbol1,
  h: () => {
    return 1
  }
}
const obj2 = deepClone(obj1)

obj1.a.b = 2
obj1.c[0].d = 2
obj1[symbol1].a = 2

console.log(obj1)
console.log(obj2)
console.log(obj1.g === obj2.g)
console.log(obj1.h === obj2.h)
console.log(obj1.h(), obj2.h())

TODO fiber 实例

JSON.stringify 结合 JSON.parse 实现 clone

js
const safeJsonClone = function (obj) {
  try {
    return JSON.parse(JSON.stringify(obj))
  } catch (err) {
    return obj
  }
}

了解此方案 clone 的局限性,需要了解这两个方法处理时有哪些特点,详细参见 [JSON, JSON.stringify 与 JSON.parse]

知识点

参考