记录干杯

​( ゜- ゜)つロ 干杯 ~
开往

使用 JavaScript 简单实现对象深拷贝

前提

由于 JavaScript 中的数据类型非常多,大部分数据都可以放在对象里,其中有很多不适合进行 Clone,比如 DOM 节点、原型链等,所以这里仅探讨 JSON 所包含的数据的深拷贝,也就是拷贝 Object 的嵌套对象和数组结构。

测试用的对象

测试用的对象分为两个,第一个是 JSON 所支持的格式,第二个包含了一些 JS 中的一些数据结构。

const obj = {
  num: 123,
  str: 'hello',
  arr: [456, 'world', [1, [2, [3]]], { a: 'A' }, false],
  obj: {
    obj: {
      obj: {
        arr: [],
      },
    },
  },
  bool: true,
  emoji: '🚀',
  中文: '测试',
  is_null: null,
}
const extend = {
  regex: /123/,
  func: function () {
    console.log('hello')
  },
  date: new Date(),
  symbol: Symbol(),
  map: new Map(),
  set: new Set(),
  is_undefined: undefined,
  not_a_num: NaN,
}

JSON.parseJSON.stringify

最简单的深拷贝方法了,缺点是只能拷贝 JSON 支持的数据,其他的比如 Map、Set 都没有被拷贝。

let justCopy = obj;
justCopy.obj === obj.obj; // true

let deepClone = JSON.parse(JSON.stringify(obj));
deepClone.obj === obj.obj; // false

JSON.parse(JSON.stringify(extend));
// output:
{
  "regex": {},
  "date": "2021-03-28T13:41:24.189Z",
  "map": {},
  "set": {},
  "not_a_num": null
}

递归拷贝

用递归的方式拷贝嵌套对象和数组,同样只有一部分的 JS 对象能被拷贝,但是对于 JSON 对象可以比较好的完成拷贝。

const cloneDeep = target => {
  // 判断如果不是 Object 或者 Array 就返回
  // 需要注意 typeof [] === 'object'
  if (typeof target !== 'object') {
    return target
  }

  // 判断整体上是对象还是数组,之后往里面拷贝属性
  let newTarget = Array.isArray(target) ? [] : {}

  // 遍历对象或者数组中的每一项(属性)
  for (let key in target) {
    // 只对对象本身的属性进行拷贝,而不拷贝集成的属性(也就是原型链中的东西)
    if (target.hasOwnProperty(key)) {
      // 这里 target[key] 既包含对象,也包含数字字符串等,因为函数开头判断过了,不是对象和数组的直接返回
      newTarget[key] = cloneDeep(target[key])
    }
  }

  return newTarget
}
cloneDeep(obj);
// output:
{
  num: 123,
  str: 'hello',
  arr: [456, 'world', [1, [2, [3]]], { a: 'A' }, false],
  obj: {
    obj: {
      obj: {
        arr: [],
      },
    },
  },
  bool: true,
  emoji: '🚀',
  中文: '测试',
  is_null: {},
};

cloneDeep(extend);
// output:
{
  regex: {},
  func: [(Function: func)],
  date: {},
  symbol: Symbol(),
  map: {},
  set: {},
  is_undefined: undefined,
  not_a_num: NaN,
};

这里的代码参考了文章:深拷贝系列 ———— 自己通过递归实现一个深拷贝,文章的作者还尝试实现了其他数据类型的深拷贝,想要深入了解的话可以去看看这个文章。

Object.assign

ES6 中新加入的方法,可以把一个或多个对象的属性拷贝到一个对象中,实现的是浅拷贝(或者说是只有一层的深拷贝)。

let justCopy = obj
justCopy.obj === obj.obj // true

let assignClone = Object.assign({}, obj)
assignClone.obj === obj.obj // true

Object.assign 的优点是可以拷贝多种类型的数据。

Object.assign({}, extend);
// output:
{
  regex: /123/,
  func: [Function: func],
  date: 2021-03-28T13:48:41.912Z,
  symbol: Symbol(),
  map: Map(0) {},
  set: Set(0) {},
  is_undefined: undefined,
  not_a_num: NaN
}

相关的库

前端库这么多,随便找找就有深拷贝的实现,直接拿来用就好了。

rfdc

Really Fast Deep Clone,npm 上下载比较多的一个库。

_.cloneDeep

Loadsh 的实现,文档:Lodash Documentation

$.extend

jQuery 也有,文档:jQuery.extend() | jQuery API Documentation

参考资料

网站正在重新设计中,部分功能和内容还没完成。