JS DeepCopy深复制的两种方式,并解决循环引用、Date、RegExp对象的深复制

日常使用的深复制都有同样的缺陷,就是只能实现特定的object的深度复制(比如数组和函数),不能解决循环引用DateRegExp对象的复制。

为什么需要DeepCopy

在JS里,除Array和Object之外的数据类型的复制可以直接通过等号=来实现,但Array和Object类型的数据通过等号只是起引用作用,指向的是同一块内存地址。当源数据改变,引用的数据也同时会发生变化。

其他方案的优缺点

JSON.parse(JSON.stringify(target))

如果obj里面有时间对象,则JSON.stringify后再JSON.parse的结果,时间将只是字符串的形式。而不是时间对象;

如果obj里有RegExp、Error对象,则序列化的结果将只得到空对象;

如果obj里有函数,undefined,则序列化的结果会把函数或 undefined丢失;

如果obj里有NaN、Infinity和-Infinity,则序列化的结果会变成null

JSON.stringify()只能序列化对象的可枚举的自有属性,例如 如果obj中的对象是有构造函数生成的, 则使用JSON.parse(JSON.stringify(obj))深拷贝后,会丢弃对象的constructor;

如果对象中存在循环引用的情况也无法正确实现深拷贝;

Object.assign

Object.assign不是简单的深拷贝。查阅官方文档发现它Object.assign只对顶层属性做了赋值,完全没有继续做递归之类的把所有下一层的属性做深拷贝。

实现一个最基本的深复制

实现了普通对象、数组和函数的深复制,但是未解决循环引用、Date、RegExp

function deepCopy(obj) {
  let _obj = Array.isArray(obj) ? [] : {}
  for (let i in obj) {
    _obj[i] = typeof obj[i] === 'object' ? deepCopy(obj[i]) : obj[i]
  }
  return _obj
}
const obj1 = {x: 2, y: {z: 3}}
obj1.fn = function add() {
  return 's'
}
const obj2 = deepCopy(obj1);
console.log(obj1)
console.log(obj2)
/*
{ x: 2, y: { z: 3 }, fn: [Function: add] }
{ x: 2, y: { z: 3 }, fn: [Function: add] }
 */

Vuex源码中的DeepCopy解决循环引用,但未解决Date和RegExp

原理:可以使用一个WeakMap结构(ES6)或者数组(ES5)存储已经被拷贝的对象,每一次进行拷贝的时候就先向WeakMap查询该对象是否已经被拷贝,如果已经被拷贝则取出该对象并返回。

function find(list, f) {
  return list.filter(f)[0]
}
function deepCopyV(obj, cache) {
  if (cache === void 0) cache = []

  // just return if obj is immutable value
  if (obj === null || typeof obj !== 'object') {
    return obj
  }

  // if obj is hit, it is in circular structure
  var hit = find(cache, function(c) { return c.original === obj })
  if (hit) {
    return hit.copy
  }

  var copy = Array.isArray(obj) ? [] : {}
  // put the copy into cache at first
  // because we want to refer it in recursive deepCopy
  cache.push({
    original: obj,
    copy: copy
  })

  Object.keys(obj).forEach(function(key) {
    copy[key] = deepCopy(obj[key], cache)
  })

  return copy
}

优化Vuex中的DeepCopy解决Date和RegExp

通过对Date和RegExp的判断解决这部分的深复制,改造上面代码,解决问题

 // just return if obj is immutable value
  const Constructor = obj.constructor
  // typeof null的返回值为object,所以可以直接省略
  if (typeof obj !== 'object') {
    return obj
  } else if (Constructor === RegExp) {
    return new Constructor(obj)
  } else if (Constructor === Date) {
    return new Constructor(obj.getTime())
  }

//打印
const obj1 = {x: 1}
const obj2 = {x: 2}
obj1.next = obj2;
obj2.next = obj1;
obj1.fn = function add() {
  return 's'
}
obj1.reg = /\s+/g
obj1.time = new Date()
const obj3 = deepCopy(obj1);
console.log(obj1)
console.log(obj2)
console.log(obj3)

输出

{ x: 1,
  next: { x: 2, next: [Circular] },
  fn: [Function: add],
  reg: /\s+/g,
  time: 2019-11-22T06:53:35.946Z }
{ x: 2,
  next:
   { x: 1,
     next: [Circular],
     fn: [Function: add],
     reg: /\s+/g,
     time: 2019-11-22T06:53:35.946Z } }
{ x: 1,
  next: { x: 2, next: [Circular] },
  fn: [Function: add],
  reg: /\s+/g,
  time: 2019-11-22T06:53:35.946Z }
*/
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章