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

資訊專欄INFORMATION COLUMN

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

flyer_dev / 2344人閱讀

摘要:上對(duì)位運(yùn)算的解釋是它經(jīng)常被用來(lái)創(chuàng)建處理以及讀取標(biāo)志位序列一種類似二進(jìn)制的變量。位運(yùn)算,常用于處理同時(shí)存在多個(gè)布爾選項(xiàng)的情形。掩碼中的每個(gè)選項(xiàng)的值都是的冪,位運(yùn)算是位的。位運(yùn)算,說(shuō)白了就是直接對(duì)某個(gè)數(shù)據(jù)在內(nèi)存中的二進(jìn)制位,進(jìn)行運(yùn)算操作。

前言

上一篇文章 「前端面試題系列9」淺拷貝與深拷貝的含義、區(qū)別及實(shí)現(xiàn) 中提到了深拷貝的實(shí)現(xiàn)方法,從遞歸調(diào)用,到 JSON,再到終極方案 cloneForce。

不經(jīng)讓我想到,lodash 中的 _.cloneDeep 方法。它是如何實(shí)現(xiàn)深拷貝的呢?今天,就讓我們來(lái)具體地解讀一下 _.cloneDeep 的源碼實(shí)現(xiàn)。

源碼中的內(nèi)容比較多,為了能將知識(shí)點(diǎn)講明白,也為了更好的閱讀體驗(yàn),將會(huì)分為上下 2 篇進(jìn)行解讀。今天主要會(huì)涉及位掩碼、對(duì)象判斷、數(shù)組和正則的深拷貝寫法。

ok,現(xiàn)在就讓我們深入源碼,共同探索吧~

_.cloneDeep 的源碼實(shí)現(xiàn)

它的源碼內(nèi)容很少,因?yàn)橹饕€是靠 baseClone 去實(shí)現(xiàn)。

/** Used to compose bitmasks for cloning. */
const CLONE_DEEP_FLAG = 1
const CLONE_SYMBOLS_FLAG = 4

function cloneDeep(value) {
  return baseClone(value, CLONE_DEEP_FLAG | CLONE_SYMBOLS_FLAG)
}

剛看到前兩行的常量就懵了,它們的用意是什么?然后,傳入 baseClone 的第二個(gè)參數(shù),似乎還將那兩個(gè)常量做了運(yùn)算,其結(jié)果是什么?這么做的目的是什么?

一番查找之后,終于明白這里其實(shí)涉及到了 位掩碼位運(yùn)算 的概念。下面就來(lái)詳細(xì)講解一下。

位掩碼技術(shù)

回到第一行注釋:Used to compose bitmasks for cloning。意思是,用于構(gòu)成克隆方法的位掩碼。

從注釋看,這里的 CLONE_DEEP_FLAGCLONE_SYMBOLS_FLAG 就是位掩碼了,而 CLONE_DEEP_FLAG | CLONE_SYMBOLS_FLAG 其實(shí)是 位運(yùn)算 中的 按位或 方法。

這里有個(gè)不常見(jiàn)的概念:位運(yùn)算。MDN 上對(duì)位運(yùn)算的解釋是:它經(jīng)常被用來(lái)創(chuàng)建、處理以及讀取標(biāo)志位序列——一種類似二進(jìn)制的變量。雖然可以使用變量代替標(biāo)志位序列,但是這樣可以節(jié)省內(nèi)存(1/32)。

不過(guò)實(shí)際開(kāi)發(fā)中,位運(yùn)算用得很少,主要是因?yàn)槲贿\(yùn)算操作的是二進(jìn)制位,對(duì)開(kāi)發(fā)者來(lái)說(shuō)不太好理解。用得少,就容易生疏。但實(shí)際上,位運(yùn)算是一種很棒的思想,它計(jì)算得更快,代碼量還更少。位運(yùn)算,常用于處理同時(shí)存在多個(gè)布爾選項(xiàng)的情形。掩碼中的每個(gè)選項(xiàng)的值都是 2 的冪,位運(yùn)算是 32 位的。

在計(jì)算機(jī)程序的世界里,所有的數(shù)據(jù)都是以二進(jìn)制的形式儲(chǔ)存的。位運(yùn)算,說(shuō)白了就是直接對(duì)某個(gè)數(shù)據(jù)在內(nèi)存中的二進(jìn)制位,進(jìn)行運(yùn)算操作。比如 &、|、~、^、>>,這些都是 按位運(yùn)算符,它們有一些神奇的用法。以系統(tǒng)權(quán)限為例:

const PERMISSION_A = 1; // 0001
const PERMISSION_B = 2; // 0010
const PERMISSION_C = 4; // 0100
const PERMISSION_D = 8; // 1000

// 當(dāng)一個(gè)用戶同時(shí)擁有 權(quán)限A 和 權(quán)限C 時(shí),就產(chǎn)生了一個(gè)新的權(quán)限
const mask = PERMISSION_A | PERMISSION_C; // 0101,十進(jìn)制為 5

// 判斷該用戶是否有 權(quán)限C,可以取出 權(quán)限C 的位掩碼
if (mask & PERMISSION_C) {
    ...
}

// 該用戶沒(méi)有 權(quán)限A,也沒(méi)有 權(quán)限C
const mask2 = ~(PERMISSION_A | PERMISSION_C); // ~0101 => 1010

// 取出 與權(quán)限A 不同的部分
const mask3 = mask ^ PERMISSION_A; // 0101 ^ 0001 => 0100

回到源碼的 CLONE_DEEP_FLAG | CLONE_SYMBOLS_FLAG 就得到一個(gè)新的結(jié)果傳入 baseClone 中,十進(jìn)制為 5,至于它是用來(lái)干什么的,就需要繼續(xù)深入到 baseClone 的源碼中去看了。

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

先貼一下源碼,其中一些關(guān)鍵的判斷已經(jīng)做了注釋

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
  }

  // 過(guò)濾出原始類型,直接返回
  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
}
位掩碼的作用
/** Used to compose bitmasks for cloning. */
const CLONE_DEEP_FLAG = 1 // 深拷貝標(biāo)志位
const CLONE_FLAT_FLAG = 2 // 原型鏈標(biāo)志位
const CLONE_SYMBOLS_FLAG = 4 // Symbol 標(biāo)志位

function baseClone(value, bitmask, customizer, key, object, stack) {
    // 根據(jù)位掩碼,取出位掩碼,切分判斷入口,bitmask 的十進(jìn)制為 5
    const isDeep = bitmask & CLONE_DEEP_FLAG // 5 & 1 => 1 => true
    const isFlat = bitmask & CLONE_FLAT_FLAG // 5 & 2 => 0 => false
    const isFull = bitmask & CLONE_SYMBOLS_FLAG // 5 & 4 => 4 => true
    ...
}

每個(gè)常量基本都加了注釋,之前傳入 baseClone 的 bitmask 為十進(jìn)制的 5,其目的就是為了在 baseClone 中進(jìn)行判斷入口的切分。

是否為對(duì)象的判斷
// 如果不是對(duì)象,則直接返回該值
if (!isObject(value)) {
    return value
}

// ./isObject.js
function isObject(value) {
  const type = typeof value
  return value != null && (type == "object" || type == "function")
}

這里需要說(shuō)的就是,是否為對(duì)象的判斷。用的基本方法是 typeof,但是因?yàn)?typeof null 的值也是 "object",所以最后的 return 需要對(duì) null 做額外處理。

處理數(shù)組和正則
const isArr = Array.isArray(value)

if (isArr) {
    result = initCloneArray(value)
    if (!isDeep) {
        return copyArray(value, result)
    }
} else {
    ... // 非數(shù)組的處理
}

// 用于檢測(cè)對(duì)象自身的屬性
const hasOwnProperty = Object.prototype.hasOwnProperty

// 初始化需要克隆的數(shù)組
function initCloneArray(array) {
    const { length } = array
    const result = new array.constructor(length)

    // Add properties assigned by `RegExp#exec`.
    if (length && typeof array[0] == "string" && hasOwnProperty.call(array, "index")) {
        result.index = array.index
        result.input = array.input
    }
    return result
}

為了不干擾源數(shù)組的數(shù)據(jù),這里首先會(huì)用 initCloneArray 初始化一個(gè)全新的數(shù)組。

其中,new array.constructor(length) 相當(dāng)于 new Array(length),只是換了種不常見(jiàn)的寫法,作用是一樣的。

接下來(lái)的這個(gè)判斷,讓我一頭霧水。

// Add properties assigned by `RegExp#exec`.
if (length && typeof array[0] == "string" && hasOwnProperty.call(array, "index")) {
    result.index = array.index
    result.input = array.input
}

判斷條件首先確定 length > 0,然后 array[0] 的類型是 string,最后 array 擁有 index 這個(gè)屬性。

看到判斷條件里的兩條執(zhí)行語(yǔ)句更懵了,需要賦值 indexinput,這又是為什么?/(ㄒoㄒ)/~~

回頭看到第一行注釋,有個(gè)關(guān)鍵點(diǎn) RegExp#exec。MDN 中給的解釋:exec() 方法在一個(gè)指定字符串中執(zhí)行一個(gè)搜索匹配。返回一個(gè)結(jié)果數(shù)組或 null。文檔下方有個(gè)例子:

var re = /quicks(brown).+?(jumps)/ig;
var result = re.exec("The Quick Brown Fox Jumps Over The Lazy Dog");
console.log(result);

// 輸出的 result 是一個(gè)數(shù)組,有 3 個(gè)元素和 4 個(gè)屬性
// 0: "Quick Brown Fox Jumps"
// 1: "Brown"
// 2: "Jumps"
// groups: undefined
// index: 4
// input: "The Quick Brown Fox Jumps Over The Lazy Dog"
// length: 3

哇哦~ 原來(lái) indexinput 在這里。所以,源碼中的為何要那樣賦值,就迎刃而解了。

再回到 baseClone 中來(lái),如果不是深拷貝,那就只要做數(shù)組的第一層數(shù)據(jù)的賦值即可。

if (!isDeep) {
    return copyArray(value, result)
}

// ./copyArray.js
function copyArray(source, array) {
  let index = -1
  const length = source.length

  array || (array = new Array(length))
  while (++index < length) {
    array[index] = source[index]
  }
  return array
}
總結(jié)

位掩碼技術(shù),是一種很棒的思想,可以寫出更為簡(jiǎn)潔的代碼,運(yùn)行得也更快。對(duì)象的判斷,需要特別注意 null,它的 typeof 值 也是 object。正則的 exec() 方法會(huì)返回一個(gè)結(jié)果數(shù)組或 null,其中就會(huì)有 index 和 input 屬性。

閱讀源碼的過(guò)程比較痛苦,深感自身的不足。從不懂到查閱資料,再到寫出來(lái),耗費(fèi)了我大量的時(shí)間,不過(guò)寫作的過(guò)程也給了我不小的收獲。修行之路任重而道遠(yuǎn),給自己打打氣,繼續(xù)砥礪前行吧。

未完待續(xù)。。。

崗位內(nèi)推

莉莉絲游戲招 高級(jí)前端 啦?。。?/p>

你玩過(guò)《小冰冰傳奇([刀塔傳奇])》么?你玩過(guò)《劍與家園》么?還有本篇的封面,為我司的新游戲《AFK arena》,現(xiàn)已占領(lǐng)各大海外應(yīng)用市場(chǎng)(友情提示:要小心,這游戲有毒嗷~)。

有興趣的同學(xué),可以 關(guān)注下面的公眾 號(hào)加我微信 詳聊哈~

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

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

相關(guān)文章

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

    摘要:用于檢測(cè)自己是否在自己的原型鏈上如果是函數(shù),則取出該函數(shù)的原型對(duì)象否則,取出對(duì)象的原型對(duì)象其中,的判斷,是為了確定的類型是對(duì)象或數(shù)組。相當(dāng)于,而的構(gòu)造函數(shù)是一個(gè)函數(shù)對(duì)象。 showImg(https://segmentfault.com/img/bVbq2N1?w=640&h=437); 前言 接著上一篇文章 lodash 是如何實(shí)現(xiàn)深拷貝的(上),今天會(huì)繼續(xù)解讀 _.cloneDee...

    zombieda 評(píng)論0 收藏0
  • js淺復(fù)制

    摘要:總結(jié)綜上所述,數(shù)組的深拷貝比較簡(jiǎn)單,方法沒(méi)有什么爭(zhēng)議,對(duì)象的深拷貝,比較好的方法是用的方法實(shí)現(xiàn),或者遞歸實(shí)現(xiàn),比較簡(jiǎn)單的深復(fù)制可以使用實(shí)現(xiàn)參考資料知乎中的深拷貝和淺拷貝深入剖析的深復(fù)制 深淺復(fù)制對(duì)比 因?yàn)镴avaScript存儲(chǔ)對(duì)象都是存地址的,所以淺復(fù)制會(huì)導(dǎo)致 obj 和obj1 指向同一塊內(nèi)存地址。我的理解是,這有點(diǎn)類似數(shù)據(jù)雙向綁定,改變了其中一方的內(nèi)容,都是在原來(lái)的內(nèi)存基礎(chǔ)上做...

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

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

    xiaoxiaozi 評(píng)論0 收藏0
  • 讀懂源碼系列2」我從 lodash 源碼中學(xué)到幾個(gè)知識(shí)點(diǎn)

    摘要:今天要講的,是我從的源碼實(shí)現(xiàn)文件中學(xué)到的幾個(gè)很基礎(chǔ),卻又容易被忽略的知識(shí)點(diǎn)。在函數(shù)式編程中,函數(shù)是一等公民,它可以只是根據(jù)參數(shù),做簡(jiǎn)單的組合操作,再作為別的函數(shù)的返回值。所以,閱讀源碼,是一種很棒的重溫基礎(chǔ)知識(shí)的方式。 showImg(https://segmentfault.com/img/bVbpTSY?w=750&h=422); 前言 上一篇文章 「前端面試題系列8」數(shù)組去重(1...

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

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

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

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

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<