成人无码视频,亚洲精品久久久久av无码,午夜精品久久久久久毛片,亚洲 中文字幕 日韩 无码

資訊專欄INFORMATION COLUMN

「讀懂源碼系列4」lodash 是如何實(shí)現(xiàn)深拷貝的(下)

zombieda / 1572人閱讀

摘要:用于檢測自己是否在自己的原型鏈上如果是函數(shù),則取出該函數(shù)的原型對(duì)象否則,取出對(duì)象的原型對(duì)象其中,的判斷,是為了確定的類型是對(duì)象或數(shù)組。相當(dāng)于,而的構(gòu)造函數(shù)是一個(gè)函數(shù)對(duì)象。

前言

接著上一篇文章 lodash 是如何實(shí)現(xiàn)深拷貝的(上),今天會(huì)繼續(xù)解讀 _.cloneDeep 的源碼,來看看 lodash 是如何處理對(duì)象、函數(shù)、循環(huán)引用等的深拷貝問題的。

baseClone 的源碼實(shí)現(xiàn)

先回顧一下它的源碼,以及一些關(guān)鍵的注釋

function baseClone(value, bitmask, customizer, key, object, stack) {
  let result
  // 根據(jù)位掩碼,切分判斷入口
  const isDeep = bitmask & CLONE_DEEP_FLAG
  const isFlat = bitmask & CLONE_FLAT_FLAG
  const isFull = bitmask & CLONE_SYMBOLS_FLAG

  // 自定義 clone 方法,用于 _.cloneWith
  if (customizer) {
    result = object ? customizer(value, key, object, stack) : customizer(value)
  }
  if (result !== undefined) {
    return result
  }

  // 過濾出原始類型,直接返回
  if (!isObject(value)) {
    return value
  }
  
  const isArr = Array.isArray(value)
  const tag = getTag(value)
  if (isArr) {
    // 處理數(shù)組
    result = initCloneArray(value)
    if (!isDeep) {
      // 淺拷貝數(shù)組
      return copyArray(value, result)
    }
  } else {
    // 處理對(duì)象
    const isFunc = typeof value == "function"
    
    if (isBuffer(value)) {
      return cloneBuffer(value, isDeep)
    }
    if (tag == objectTag || tag == argsTag || (isFunc && !object)) {
      result = (isFlat || isFunc) ? {} : initCloneObject(value)
      if (!isDeep) {
        return isFlat
          ? copySymbolsIn(value, copyObject(value, keysIn(value), result))
          : copySymbols(value, Object.assign(result, value))
      }
    } else {
      if (isFunc || !cloneableTags[tag]) {
        return object ? value : {}
      }
      result = initCloneByTag(value, tag, isDeep)
    }
  }
  // 用 “?!?處理循環(huán)引用
  stack || (stack = new Stack)
  const stacked = stack.get(value)
  if (stacked) {
    return stacked
  }
  stack.set(value, result)

  // 處理 Map
  if (tag == mapTag) {
    value.forEach((subValue, key) => {
      result.set(key, baseClone(subValue, bitmask, customizer, key, value, stack))
    })
    return result
  }

  // 處理 Set
  if (tag == setTag) {
    value.forEach((subValue) => {
      result.add(baseClone(subValue, bitmask, customizer, subValue, value, stack))
    })
    return result
  }

  // 處理 typedArray
  if (isTypedArray(value)) {
    return result
  }

  const keysFunc = isFull
    ? (isFlat ? getAllKeysIn : getAllKeys)
    : (isFlat ? keysIn : keys)

  const props = isArr ? undefined : keysFunc(value)

  // 遍歷賦值
  arrayEach(props || value, (subValue, key) => {
    if (props) {
      key = subValue
      subValue = value[key]
    }
    // Recursively populate clone (susceptible to call stack limits).
    assignValue(result, key, baseClone(subValue, bitmask, customizer, key, value, stack))
  })

  return result
}
處理對(duì)象和函數(shù)

一些主要的判斷入口,已經(jīng)加上了注釋。

const isArr = Array.isArray(value)
const tag = getTag(value)

if (isArr) {
    ... // 剛才數(shù)組的處理
} else {
    // 開始處理對(duì)象
    // 對(duì)象是函數(shù)的標(biāo)志位
    const isFunc = typeof value == "function"
    
    // 處理 Buffer(緩沖區(qū))對(duì)象
    if (isBuffer(value)) {
        return cloneBuffer(value, isDeep)
    }
    
    // 如果 tag 是 "[object Object]"
    // 或 tag 是 "[object Arguments]"
    // 或 是函數(shù)但沒有父對(duì)象(object 由 baseClone 傳入,是 value 的父對(duì)象)
    if (tag == objectTag || tag == argsTag || (isFunc && !object)) {
        // 初始化 result
        // 如果是原型鏈或函數(shù)時(shí),設(shè)置為空對(duì)象
        // 否則,新開一個(gè)對(duì)象,并將源對(duì)象的鍵值對(duì)依次拷貝進(jìn)去
        result = (isFlat || isFunc) ? {} : initCloneObject(value)
        if (!isDeep) {
            // 進(jìn)入對(duì)象的淺拷貝
            return isFlat
            // 如果是原型鏈,則需要拷貝自身,還有繼承的 symbols
            ? copySymbolsIn(value, copyObject(value, keysIn(value), result))
            // 否則,只要拷貝自身的 symbols
            : copySymbols(value, Object.assign(result, value))
        }
    } else {
        // 是函數(shù) 或者 不是error類型 或者 不是weakmap類型時(shí)
        if (isFunc || !cloneableTags[tag]) {
            return object ? value : {}
        }
        // 按需要初始化 cloneableTags 對(duì)象中剩余的類型
        result = initCloneByTag(value, tag, isDeep)
    }
}

其中,isBuffer 會(huì)處理 Buffer 類的拷貝,它是 Node.js 中的概念,用來創(chuàng)建一個(gè)專門存放二進(jìn)制數(shù)據(jù)的緩存區(qū),可以讓 Node.js 處理二進(jìn)制數(shù)據(jù)。

在 baseClone 的外面,還定義了一個(gè)對(duì)象 cloneableTags,里面只有 error 和 weakmap 類型會(huì)返回 false,所以 !cloneableTags[tag] 的意思就是,不是 error 或 weakmap 類型。

接下來,來看如何初始化一個(gè)新的 Object 對(duì)象。

function initCloneObject(object) {
    return (typeof object.constructor == "function" && !isPrototype(object))
        ? Object.create(Object.getPrototypeOf(object))
        : {}
}

// ./isPrototype.js
const objectProto = Object.prototype
// 用于檢測自己是否在自己的原型鏈上
function isPrototype(value) {
    const Ctor = value && value.constructor
    // 如果 value 是函數(shù),則取出該函數(shù)的原型對(duì)象
    // 否則,取出對(duì)象的原型對(duì)象
    const proto = (typeof Ctor == "function" && Ctor.prototype) || objectProto

    return value === proto
}

其中,typeof object.constructor == "function" 的判斷,是為了確定 value 的類型是對(duì)象或數(shù)組。

然后用 Object.create 生成新的對(duì)象。Object.create() 方法用于創(chuàng)建一個(gè)新對(duì)象,使用現(xiàn)有的對(duì)象來提供新創(chuàng)建的對(duì)象的 __proto__。

object.constructor 相當(dāng)于 new Object(),而 Object 的構(gòu)造函數(shù)是一個(gè)函數(shù)對(duì)象。

const obj = new Object();

console.log(typeof obj.constructor);
// "function"

對(duì)象的原型,可以通過 Object.getPrototypeOf(obj) 獲取,它相當(dāng)于過去使用的 __proto__。

initCloneByTag 方法會(huì)處理剩余的多種類型的拷貝,有原始類型,也有如 dateTag、dataViewTag、float32Tagint16Tag、mapTagsetTag、regexpTag 等等。

其中,cloneTypedArray 方法用于拷貝類型數(shù)組。類型數(shù)組,是一種類似數(shù)組的對(duì)象,它由 ArrayBuffer、TypedArray、DataView 三類對(duì)象構(gòu)成,通過這些對(duì)象為 JavaScript 提供了訪問二進(jìn)制數(shù)據(jù)的能力。

循環(huán)引用
// 如果有 stack 作為參數(shù)傳入,就用參數(shù)中的 stack
// 不然就 new 一個(gè) Stack
stack || (stack = new Stack)
const stacked = stack.get(value)
if (stacked) {
    return stacked
}
stack.set(value, result)

與 「前端面試題系列9」淺拷貝與深拷貝的含義、區(qū)別及實(shí)現(xiàn) 最后提到的 cloneForce 方案類似,利用了棧來解決循環(huán)引用的問題。

如果 stacked 有值,則表明已經(jīng)在棧中存在,不然就 valueresult 入棧。在 Stack 中的 set 方法:

constructor(entries) {
    const data = this.__data__ = new ListCache(entries)
    this.size = data.size
}

set(key, value) {
    let data = this.__data__
    // data 是否在 ListCache 的構(gòu)造函數(shù)中存在
    if (data instanceof ListCache) {
        const pairs = data.__data__
        // LARGE_ARRAY_SIZE 為 200
        if (pairs.length < LARGE_ARRAY_SIZE - 1) {
            pairs.push([key, value])
            this.size = ++data.size
            return this
        }
        // 超出200,則重置 data
        data = this.__data__ = new MapCache(pairs)
    }
    // data 不在 ListCache 的構(gòu)造函數(shù)中,則直接進(jìn)行 set 操作
    data.set(key, value)
    this.size = data.size
    return this
}
Map 和 Set

這兩個(gè)類型的深拷貝利用了遞歸的思想,只是添加元素的方式有區(qū)別,Mapset,Setadd

if (tag == mapTag) {
    value.forEach((subValue, key) => {
      result.set(key, baseClone(subValue, bitmask, customizer, key, value, stack))
    })
    return result
  }

  if (tag == setTag) {
    value.forEach((subValue) => {
      result.add(baseClone(subValue, bitmask, customizer, subValue, value, stack))
    })
    return result
}
Symbol 和 原型鏈
// 獲取數(shù)組 keys
const keysFunc = isFull
    ? (isFlat ? getAllKeysIn : getAllKeys)
    : (isFlat ? keysIn : keys)

const props = isArr ? undefined : keysFunc(value)
arrayEach(props || value, (subValue, key) => {
    // 如果 props 有值,則替換 key 和 subValue
    if (props) {
        key = subValue
        subValue = value[key]
    }
    // 遞歸克?。ㄒ资苷{(diào)用堆棧限制)
    assignValue(result, key, baseClone(subValue, bitmask, customizer, key, value, stack))
})

return result

// ./getAllKeysIn
// 返回一個(gè)包含 自身 和 原型鏈上的屬性名 以及Symbol 的數(shù)組
function getAllKeysIn(object) {
    const result = []
    for (const key in object) {
        result.push(key)
    }
    if (!Array.isArray(object)) {
        result.push(...getSymbolsIn(object))
    }
    return result
}

// ./getAllKeys
// 返回一個(gè)包含 自身 和 Symbol 的數(shù)組
function getAllKeys(object) {
    const result = keys(object)
    if (!Array.isArray(object)) {
        result.push(...getSymbols(object))
    }
    return result
}

// ./keysIn
// 返回一個(gè) 自身 和 原型鏈上的屬性名 的數(shù)組
function keysIn(object) {
    const result = []
    for (const key in object) {
        result.push(key)
    }
    return result
}

// ./keys
// 返回一個(gè) 自身屬性名 的數(shù)組
function keys(object) {
    return isArrayLike(object)
        ? arrayLikeKeys(object)
        : Object.keys(Object(object))
}

最后來看下 assignValue 的實(shí)現(xiàn)。

// ./assignValue
const hasOwnProperty = Object.prototype.hasOwnProperty

function assignValue(object, key, value) {
  const objValue = object[key]

  if (!(hasOwnProperty.call(object, key) && eq(objValue, value))) {
    // value 非零或者可用
    if (value !== 0 || (1 / value) == (1 / objValue)) {
      baseAssignValue(object, key, value)
    }
  // value 未定義,并且 object 中沒有 key
  } else if (value === undefined && !(key in object)) {
    baseAssignValue(object, key, value)
  }
}

// ./baseAssignValue
// 賦值的基礎(chǔ)實(shí)現(xiàn)
function baseAssignValue(object, key, value) {
  if (key == "__proto__") {
    Object.defineProperty(object, key, {
      "configurable": true,
      "enumerable": true,
      "value": value,
      "writable": true
    })
  } else {
    object[key] = value
  }
}

// ./eq
// 比較兩個(gè)值是否相等
function eq(value, other) {
  return value === other || (value !== value && other !== other)
}

最后的 eq 方法中的判斷 value !== value && other !== other,這樣的寫法是為了判斷 NaN。具體的可以參考這篇 「讀懂源碼系列2」我從 lodash 源碼中學(xué)到的幾個(gè)知識(shí)點(diǎn)

總結(jié)

cloneDeep 中囊括了各種類型的深拷貝方法,比如 node 中的 buffer,類型數(shù)組等。用了棧的思想,解決循環(huán)引用的問題。Map 和 Set 的添加元素方法比較類似,分別為 set 和 add。NaN 是不等于自身的。

深拷貝的源碼解讀,到此已經(jīng)完結(jié)了。本篇的寫作過程,同樣地耗費(fèi)了好幾個(gè)晚上的時(shí)間,感覺真的是自己在跟自己較勁。只因?yàn)槲蚁氡M可能地把源碼的實(shí)現(xiàn)過程說明白,其中查找資料外加理解思考,就耗費(fèi)了許多時(shí)間,好在最終沒有放棄,收獲也是頗豐的,一些從源碼中學(xué)到的技巧,也被我用到了實(shí)際項(xiàng)目中,提升了性能與可讀性。。

近階段因?yàn)楣ぷ髟?,寫文章有所懈怠了,痛定思痛還是要繼續(xù)寫下去。自此,《超哥前端小棧》恢復(fù)更新,同時(shí)每篇文章也會(huì)同步更新到 掘金、segmentfault 和 github 上。

個(gè)人的時(shí)間精力有限,在表述上有紕漏的地方,還望讀者能多加指正,多多支持,期待能有更多的交流,感謝~

PS:歡迎關(guān)注我的公眾號(hào) “超哥前端小?!保涣鞲嗟南敕ㄅc技術(shù)。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/105730.html

相關(guān)文章

  • 讀懂源碼系列3」lodash 如何實(shí)現(xiàn)拷貝(上)

    摘要:上對(duì)位運(yùn)算的解釋是它經(jīng)常被用來創(chuàng)建處理以及讀取標(biāo)志位序列一種類似二進(jìn)制的變量。位運(yùn)算,常用于處理同時(shí)存在多個(gè)布爾選項(xiàng)的情形。掩碼中的每個(gè)選項(xiàng)的值都是的冪,位運(yùn)算是位的。位運(yùn)算,說白了就是直接對(duì)某個(gè)數(shù)據(jù)在內(nèi)存中的二進(jìn)制位,進(jìn)行運(yùn)算操作。 showImg(https://segmentfault.com/img/bVbrC56?w=2208&h=1242); 前言 上一篇文章 「前端面試題...

    flyer_dev 評(píng)論0 收藏0
  • JS拷貝

    摘要:引用類型之所以會(huì)出現(xiàn)深淺拷貝的問題,實(shí)質(zhì)上是由于對(duì)基本類型和引用類型的處理不同。另外方法可以視為數(shù)組對(duì)象的淺拷貝。上面描述過的復(fù)雜問題依然存在,可以說是最簡陋但是日常工作夠用的深拷貝方式。 一直想梳理下工作中經(jīng)常會(huì)用到的深拷貝的內(nèi)容,然而遍覽了許多的文章,卻發(fā)現(xiàn)對(duì)深拷貝并沒有一個(gè)通用的完美實(shí)現(xiàn)方式。因?yàn)閷?duì)深拷貝的定義不同,實(shí)現(xiàn)時(shí)的edge case過多,在深拷貝的時(shí)候會(huì)出現(xiàn)循環(huán)引用等問...

    xiaoxiaozi 評(píng)論0 收藏0
  • JavaScript 拷貝

    摘要:深拷貝是一件看起來很簡單的事情,但其實(shí)一點(diǎn)兒也不簡單。我們也可以利用這個(gè)實(shí)現(xiàn)對(duì)象的深拷貝。而是利用之前已經(jīng)拷貝好的值。深拷貝的詳細(xì)的源碼可以在這里查看。大功告成我們雖然的確解決了深拷貝的大部分問題。 js深拷貝是一件看起來很簡單的事情,但其實(shí)一點(diǎn)兒也不簡單。對(duì)于循環(huán)引用的問題還有一些內(nèi)置數(shù)據(jù)類型的拷貝,如Map, Set, RegExp, Date, ArrayBuffer 和其他內(nèi)置...

    zhangwang 評(píng)論0 收藏0
  • 【進(jìn)階4-1期】詳細(xì)解析賦值、淺拷貝拷貝區(qū)別

    摘要:展開語法木易楊通過代碼可以看出實(shí)際效果和是一樣的。木易楊可以看出,改變之后的值并沒有發(fā)生變化,但改變之后,相應(yīng)的的值也發(fā)生變化。深拷貝使用場景木易楊完全改變變量之后對(duì)沒有任何影響,這就是深拷貝的魔力。木易楊情況下,轉(zhuǎn)換結(jié)果不正確。 一、賦值(Copy) 賦值是將某一數(shù)值或?qū)ο筚x給某個(gè)變量的過程,分為下面 2 部分 基本數(shù)據(jù)類型:賦值,賦值之后兩個(gè)變量互不影響 引用數(shù)據(jù)類型:賦址,兩個(gè)...

    silvertheo 評(píng)論0 收藏0
  • JavaScript系列--淺析JavaScript解析賦值、淺拷貝拷貝區(qū)別

    摘要:它將返回目標(biāo)對(duì)象。有些文章說是深拷貝,其實(shí)這是不正確的。深拷貝相比于淺拷貝速度較慢并且花銷較大??截惽昂髢蓚€(gè)對(duì)象互不影響。使用深拷貝的場景完全改變變量之后對(duì)沒有任何影響,這就是深拷貝的魔力。 一、賦值(Copy) 賦值是將某一數(shù)值或?qū)ο筚x給某個(gè)變量的過程,分為: 1、基本數(shù)據(jù)類型:賦值,賦值之后兩個(gè)變量互不影響 2、引用數(shù)據(jù)類型:賦址,兩個(gè)變量具有相同的引用,指向同一個(gè)對(duì)象,相互之間有...

    laznrbfe 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

閱讀需要支付1元查看
<