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 }
*/
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章