JavaScript 中的深拷貝

什麼是深拷貝?

JavaScript中存在三種可執行代碼,global,function,eval。可執行代碼的執行依賴於執行上下文。咱們能夠抽象的將執行上下文理解爲一個對象。這個對象上會包含一些屬性,如variable object(變量對象),this value(this指針),scope chain(做用域鏈)。前端

JavaScript中的基礎數據類型都是存儲在變量對象中。JavaScript中共有5種基礎數據類型,Undefined、Null、Boolean、Number、String。咱們能夠直接操做存儲在變量對象中的,基礎數據類型。git

JavaScript中的引用數據類型的值,是保存在堆內存中的。咱們不能直接操做堆內存空間。咱們經過操做variable object中的數據的引用,訪問修改對象。這裏的應用是堆內存空間中的地址。github

image

當咱們使用 var a = b賦值的時候,若是b是引用類型,咱們賦於a的只是內存空間的地址。正則表達式

image

這就是產生了一個問題,咱們該如何複製引用類型的對象。數據結構

常見的深拷貝的方式

JSON.stringify 和 JSON.parse

最簡單的方式將對象使用JSON.stringify將對象序列化爲JSON格式的字符串,而後使用JSON.parse將JSON字符串反序列化爲對象。app

image

for in 配合 遞歸

另一種方式是使用for in循環,配合遞歸函數

image

深拷貝中遇到的問題

到這裏,問題解決了嗎?並無,上面方式有如下的問題ui

  1. JSON以及for…in沒法對Date,正則,Set,Map等數據結構作出正確的處理
  2. 沒有對Symbol的屬性作出正確的處理。
  3. for…in沒有對對象中可能存在的循環引用作出正確的處理。

如何解決這些問題?

對於上面問題的解決, 我參考了lodash的源碼,以及ramda的源碼。lodash的源碼中涉及到很是多的邊界條件的處理,可讀性相對差一些。我在此基礎上作出了必定的簡化,具體代碼在文末。this

Date的的處理

當咱們檢測到value的爲[object Date]類型的時候,咱們經過Date實例的constructor屬性,獲取實例的構造函數。Date的構造函數能夠接收一個Date的實例做爲參數。從而建立一個新的Date實例。spa

image

Map,Set的處理

和Date對象同理,使用Map或者Set實例的constructor屬性,建立新的實例後,經過forEach對Map和Set進行遍歷,從新設置Map以及Set的內容。

image

image

正則的處理

  • source,正則對象的源模式文本
  • global, global屬性代表正則表達式是否使用了"g"標誌
  • ignoreCase, ignoreCase屬性代表正則表達式是否使用了"i"標誌
  • multiline, multiline屬性代表正則表達式是否使用了"m"標誌
  • sticky, sticky屬性代表正則表達式是否使用了"y"標誌
  • unicode, unicode屬性代表正則表達式帶有"u"標

image

循環引用的處理

當出現循環引用的時候,若是不進行判斷,使用遞歸。會爆棧

image

WeakMap支持使用引用類型做爲key,咱們能夠利用這個特性,將對象的每個引用類型的屬性做爲key,存儲在WeakMap中。當一樣的key再次出現時,能夠證實發生了循環引用。直接返回結果。

image

image

Symbol屬性的處理

Symbols類型的屬性沒法使用Object.keys獲取,能夠經過Object.getOwnPropertySymbols方法獲取對象上的Symbol類型的屬性。

image

參考

源碼

// 使用WeakMap判斷是否造成了環,避免爆棧
const hash = new WeakMap()

function hasHash (value) {
  return hash.has(value)
}

function setHash (value, result) {
  hash.set(value, result)
}

function getHash (value) {
  return hash.get(value)
}

function getSymbols (value) {
  let symKeys = []
  const propertyIsEnumerable = Object.prototype.propertyIsEnumerable
  const nativeGetSymbols = Object.getOwnPropertySymbols
  symKeys = nativeGetSymbols(value)
  // 判斷是不是可枚舉的屬性
  symKeys = symKeys.filter(symkey => propertyIsEnumerable.call(value, symkey))
  return symKeys
}

function getAllKeys (value) {
  let keys = Object.keys(value)
  keys = [...keys, ...getSymbols(value)]
  return keys
}

const types = {
  '[object Array]': true,
  '[object Boolean]': true,
  '[object Date]': true,
  '[object Map]': true,
  '[object Set]': true,
  '[object Number]': true,
  '[object Object]': true,
  '[object RegExp]': true,
  '[object Symbol]': true,
  '[object String]': true,
  '[object ArrayBuffer]': true,
  '[object Function]': true,
  '[object WeakMap]': false,
  '[object Error]': false
}

function isObject (value) {
  const type = typeof value;
  return value != null && (type === 'object' || type === 'function')
}

function getType (value) {
  return Object.prototype.toString.call(value);
}

function initCloneArray (value) {
  const { length } = value
  return new value.constructor(length)
}

function initCloneArrayBuffer (value) {
  const result = new value.constructor(value.byteLength)
  new Uint8Array(result).set(new Uint8Array(value))
  return result
}

function initCloneObject (value) {
  return Object.create(Object.getPrototypeOf(value))
}

function initCloneRegExp (value) {
  return new RegExp(value.source,
    (value.global     ? 'g' : '') +
    (value.ignoreCase ? 'i' : '') +
    (value.multiline  ? 'm' : '') +
    (value.sticky     ? 'y' : '') +
    (value.unicode    ? 'u' : '')
  )
}

function initCloneFunction (value) {
  return function (...args) {
    return value.apply(null, ...args)
  }
}

function initClone (value, type) {
  const Ctor = value.constructor
  switch (type) {
    case '[object ArrayBuffer]':
      return initCloneArrayBuffer(value)
    case '[object Date]':
    case '[object Map]':
    case '[object Set]':
      return new Ctor(value)
    case '[object RegExp]':
      return initCloneRegExp(value)
    case '[object Array]':
      return initCloneArray(value)
    case '[object Object]':
      return initCloneObject(value)
    case '[object Function]':
      return initCloneFunction(value)
  }
}

export default function deepClone (value) {

  let result;

  const type = getType(value)

  // 若是不是引用類型直接返回
  if (!isObject(value)) {
    return value
  }

  // 若是是weakMap,Error類型直接返回
  if (!types[type]) {
    return value
  }

  let isArr = Array.isArray(value)

  result = initClone(value, type)

  // 判斷是否產生了循環引用, 避免爆棧
  if (hasHash(value)) {
    // 若是存在直接返回
    return getHash(value)
  }
  // 在weakMap添加標記
  setHash(value, result)

  if (type === '[object Map]') {
    value.forEach((val, key) => {
      result.set(key, deepClone(val))
    })
    return result
  }

  if (type === '[object Set]') {
    value.forEach((val, key) => {
      result.add(deepClone(val))
    })
    return result
  }

  const props = getAllKeys(value)

  for (let i = 0; i < props.length; i++) {
    const key = props[i]
    const val = value[key]
    result[key] = deepClone(val)
  }

  return result
}

複製代碼
相關文章
相關標籤/搜索