稍微讀一下Lodash中isEqual的實(shí)現(xiàn)

為啥想要了解

前幾天看了下React.PureComponentshouldComponentUpdate的默認(rèn)實(shí)現(xiàn),因?yàn)槲臋n中只說(shuō)是淺比較,就想知道有多淺,又提到不要用JSON.stringify()這種方法,耗費(fèi)性能,所以我好奇lodash的isEqual的實(shí)現(xiàn)

正文

版本4.17.15

第一步

    function isEqual(value, other) {
      return baseIsEqual(value, other);
    }

第二步

    function baseIsEqual(value, other, bitmask, customizer, stack) {
      if (value === other) {
        return true;
      }
      if (value == null || other == null || (!isObjectLike(value) && !isObjectLike(other))) {
        return value !== value && other !== other;
      }
      return baseIsEqualDeep(value, other, bitmask, customizer, baseIsEqual, stack);
    }

baseIsEqual(value, other); 沒(méi)有傳bitmask, customizer, stack,暫時(shí)也不用理會(huì)!

isObjectLike 是這樣的

    function isObjectLike(value) {
      return value != null && typeof value == 'object';
    }

if (value == null || other == null || (!isObjectLike(value) && !isObjectLike(other))) { ,因?yàn)榍懊嬗眠^(guò)全等比較了,所以在這里valueother 有任意一個(gè)是 nullundefined都不可能相等,當(dāng)valueother 都不是對(duì)象的時(shí)候也會(huì)進(jìn)入,此時(shí)我只能想到NaN 會(huì)使得value !== value && other !== other; 有意義。
我腳得這樣寫(xiě)更清晰些,有些囂張!

if (value == null || other == null){
  return false;
}
if (!isObjectLike(value) && !isObjectLike(other)) {
  let isNaN = value !== value && other !== other;
  return isNaN;
}

_.isEqual(undefined, null)為false,盡管null == undefined為true
_.isEqual(Number.NaN, Number.NaN)為true,盡管NaN === NaN為false
那么問(wèn)題來(lái)了,如何實(shí)現(xiàn)一個(gè)isNaN函數(shù),記得之前用的是Number.NaN+""

第三步

注意 baseIsEqualDeep(value, other, bitmask, customizer, baseIsEqual, stack); 參數(shù)中baseIsEqual 就是當(dāng)前函數(shù),所以下面equalFunc是有值得
baseIsEqualDeep 的實(shí)現(xiàn)

    function baseIsEqualDeep(object, other, bitmask, customizer, equalFunc, stack) {
      var objIsArr = isArray(object),
          othIsArr = isArray(other),
          objTag = objIsArr ? arrayTag : getTag(object),
          othTag = othIsArr ? arrayTag : getTag(other);

      objTag = objTag == argsTag ? objectTag : objTag;
      othTag = othTag == argsTag ? objectTag : othTag;

      var objIsObj = objTag == objectTag,
          othIsObj = othTag == objectTag,
          isSameTag = objTag == othTag;

      if (isSameTag && isBuffer(object)) {
        if (!isBuffer(other)) {
          return false;
        }
        objIsArr = true;
        objIsObj = false;
      }
      if (isSameTag && !objIsObj) {
        stack || (stack = new Stack);
        return (objIsArr || isTypedArray(object))
          ? equalArrays(object, other, bitmask, customizer, equalFunc, stack)
          : equalByTag(object, other, objTag, bitmask, customizer, equalFunc, stack);
      }
      if (!(bitmask & COMPARE_PARTIAL_FLAG)) {
        var objIsWrapped = objIsObj && hasOwnProperty.call(object, '__wrapped__'),
            othIsWrapped = othIsObj && hasOwnProperty.call(other, '__wrapped__');

        if (objIsWrapped || othIsWrapped) {
          var objUnwrapped = objIsWrapped ? object.value() : object,
              othUnwrapped = othIsWrapped ? other.value() : other;

          stack || (stack = new Stack);
          return equalFunc(objUnwrapped, othUnwrapped, bitmask, customizer, stack);
        }
      }
      if (!isSameTag) {
        return false;
      }
      stack || (stack = new Stack);
      return equalObjects(object, other, bitmask, customizer, equalFunc, stack);
    }

蓬松的頭發(fā),淡黃的裙子,突然變態(tài)起來(lái)。
arrayTag = '[object Array]',
isArray 就是 Array.isArray

image.png

arrayTag = '[object Array]', 這個(gè)好找一些
getTag就是baseGetTag
image.png

    function baseGetTag(value) {
      if (value == null) {
        return value === undefined ? undefinedTag : nullTag;
      }
      return (symToStringTag && symToStringTag in Object(value))
        ? getRawTag(value)
        : objectToString(value);
    }

objectToString 本質(zhì)上就是Object.prototype.toString.call

image.png

image.png

image.png

undefinedTag = '[object Undefined]', 、nullTag = '[object Null]', ,調(diào)用Object.prototype.toString.call 得到的
symToStringTag 如果瀏覽器不支持Symbol類(lèi)型,為undefined,

image.png

Symbol.toStringTag 是一個(gè)內(nèi)置的Symbol, Map、和Promise能夠被Object.prototype.toString() 識(shí)別是因?yàn)橐鏋樗麄冊(cè)O(shè)置好了Symbol.toStringTag這個(gè)值,同樣,你可以為自己得對(duì)象添加,詳情可點(diǎn)擊此處
Object(value) 可以把基本類(lèi)型轉(zhuǎn)換為對(duì)象
image.png

可知 _.isEqual(new Map([[1, 2]]), { name: 2 }) 能夠進(jìn)入 getRawTag

    function getRawTag(value) {
      var isOwn = hasOwnProperty.call(value, symToStringTag),
          tag = value[symToStringTag];

      try {
        value[symToStringTag] = undefined;
        var unmasked = true;
      } catch (e) {}

      var result = nativeObjectToString.call(value);
      if (unmasked) {
        if (isOwn) {
          value[symToStringTag] = tag;
        } else {
          delete value[symToStringTag];
        }
      }
      return result;
    }

result = nativeObjectToString.call(value);小朋友,你會(huì)不會(huì)有很多得問(wèn)號(hào)?最后返回得也是result,針對(duì)value[symToStringTag]得一系列操作暫時(shí)看不出端倪
value 類(lèi)型為Map時(shí),
tag = value[symToStringTag];Map
unmasked = true; 執(zhí)行了,value[symToStringTag] = undefined;delete value[symToStringTag];都執(zhí)行,但是沒(méi)有效果

回到baseIsEqualDeep
argsTag = '[object Arguments]', 前面的邏輯主要是為了區(qū)分?jǐn)?shù)組還是對(duì)象,以及他們是否為相同的標(biāo)簽,注意不數(shù)據(jù)類(lèi)型,如果不是就退出比較。

image.png

需要注意的是,如果類(lèi)型為argsTag,則會(huì)判定為objectTag
這個(gè)isBuffer 是用在nodejs上的
此時(shí)我們來(lái)到了

      if (isSameTag && !objIsObj) {
        stack || (stack = new Stack);
        return (objIsArr || isTypedArray(object))
          ? equalArrays(object, other, bitmask, customizer, equalFunc, stack)
          : equalByTag(object, other, objTag, bitmask, customizer, equalFunc, stack);
      }

注意stack有值了,
isTypedArray 在瀏覽器下的實(shí)現(xiàn)為

    function baseIsTypedArray(value) {
      return isObjectLike(value) &&
        isLength(value.length) && !!typedArrayTags[baseGetTag(value)];
    }

isLength 的實(shí)現(xiàn)


    function isLength(value) {
      return typeof value == 'number' &&
        value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER;
    }

typedArrayTags 長(zhǎng)這樣

image.png

_.isEqual(new Int8Array(32), new Int8Array(32))_.isEqual([1, 2], [1, 2]) 能進(jìn)入equalArrays

    function equalArrays(array, other, bitmask, customizer, equalFunc, stack) {
      var isPartial = bitmask & COMPARE_PARTIAL_FLAG,
          arrLength = array.length,
          othLength = other.length;

      if (arrLength != othLength && !(isPartial && othLength > arrLength)) {
        return false;
      }
      // Assume cyclic values are equal.
      var stacked = stack.get(array);
      if (stacked && stack.get(other)) {
        return stacked == other;
      }
      var index = -1,
          result = true,
          seen = (bitmask & COMPARE_UNORDERED_FLAG) ? new SetCache : undefined;

      stack.set(array, other);
      stack.set(other, array);

      // Ignore non-index properties.
      while (++index < arrLength) {
        var arrValue = array[index],
            othValue = other[index];

        if (customizer) {
          var compared = isPartial
            ? customizer(othValue, arrValue, index, other, array, stack)
            : customizer(arrValue, othValue, index, array, other, stack);
        }
        if (compared !== undefined) {
          if (compared) {
            continue;
          }
          result = false;
          break;
        }
        // Recursively compare arrays (susceptible to call stack limits).
        if (seen) {
          if (!arraySome(other, function(othValue, othIndex) {
                if (!cacheHas(seen, othIndex) &&
                    (arrValue === othValue || equalFunc(arrValue, othValue, bitmask, customizer, stack))) {
                  return seen.push(othIndex);
                }
              })) {
            result = false;
            break;
          }
        } else if (!(
              arrValue === othValue ||
                equalFunc(arrValue, othValue, bitmask, customizer, stack)
            )) {
          result = false;
          break;
        }
      }
      stack['delete'](array);
      stack['delete'](other);
      return result;
    }

到這里我已經(jīng)快吐了!
這次是equalFuncstack有值,如果長(zhǎng)度不相等就會(huì)退出,先排除stack的一些邏輯,后續(xù)就是按照下標(biāo)去比較。
arrValue === othValue || equalFunc(arrValue, othValue, bitmask, customizer, stack),如果值不全等,會(huì)調(diào)用equalFunc,就是baseIsEqual做深度比較
回到baseIsEqualDeep
_.isEqual(new Map([[1, 2]]), new Map([[1, 2]])) 會(huì)觸發(fā)equalByTag

    function equalByTag(object, other, tag, bitmask, customizer, equalFunc, stack) {
      switch (tag) {
        case dataViewTag:
          if ((object.byteLength != other.byteLength) ||
              (object.byteOffset != other.byteOffset)) {
            return false;
          }
          object = object.buffer;
          other = other.buffer;

        case arrayBufferTag:
          if ((object.byteLength != other.byteLength) ||
              !equalFunc(new Uint8Array(object), new Uint8Array(other))) {
            return false;
          }
          return true;

        case boolTag:
        case dateTag:
        case numberTag:
          // Coerce booleans to `1` or `0` and dates to milliseconds.
          // Invalid dates are coerced to `NaN`.
          return eq(+object, +other);

        case errorTag:
          return object.name == other.name && object.message == other.message;

        case regexpTag:
        case stringTag:
          // Coerce regexes to strings and treat strings, primitives and objects,
          // as equal. See http://www.ecma-international.org/ecma-262/7.0/#sec-regexp.prototype.tostring
          // for more details.
          return object == (other + '');

        case mapTag:
          var convert = mapToArray;

        case setTag:
          var isPartial = bitmask & COMPARE_PARTIAL_FLAG;
          convert || (convert = setToArray);

          if (object.size != other.size && !isPartial) {
            return false;
          }
          // Assume cyclic values are equal.
          var stacked = stack.get(object);
          if (stacked) {
            return stacked == other;
          }
          bitmask |= COMPARE_UNORDERED_FLAG;

          // Recursively compare objects (susceptible to call stack limits).
          stack.set(object, other);
          var result = equalArrays(convert(object), convert(other), bitmask, customizer, equalFunc, stack);
          stack['delete'](object);
          return result;

        case symbolTag:
          if (symbolValueOf) {
            return symbolValueOf.call(object) == symbolValueOf.call(other);
          }
      }
      return false;
    }

似乎到了熟悉的地方,注意:
dataViewTag、arrayBufferTag是一對(duì)。
boolTag、dateTagnumberTag 是一起的
eq(+object, +other);做的驗(yàn)證
_.isEqual(new Number(1), 1) 他們會(huì)獲得同樣的標(biāo)簽,也會(huì)走到這里,因?yàn)?code>Object.prototype.toString.call兩者返回的標(biāo)簽一樣

function eq(value, other) {
  return value === other || (value !== value && other !== other);
}

errorTag 比較了namemessage兩個(gè)屬性
mapTagsetTag,讀到這里我裂開(kāi)了呀,如果匹配到mapTag也會(huì)執(zhí)行 setTag下面的代碼,
我只記得

    switch ("0") {
      case "0":

      case "1":

      case "2":
        console.log("120");
    }

最后轉(zhuǎn)換為數(shù)組再調(diào)用equalArrays

  function mapToArray(map) {
    var index = -1,
        result = Array(map.size);

    map.forEach(function(value, key) {
      result[++index] = [key, value];
    });
    return result;
  }
function setToArray(set) {
    var index = -1,
        result = Array(set.size);

    set.forEach(function(value) {
      result[++index] = value;
    });
    return result;
  }

感嘆擴(kuò)展運(yùn)算符的強(qiáng)大!
再次回到baseIsEqualDeep,看一下equalObjects
_.isEqual({ name: 1 }, { name: 2 })會(huì)進(jìn)入

    function equalObjects(object, other, bitmask, customizer, equalFunc, stack) {
      var isPartial = bitmask & COMPARE_PARTIAL_FLAG,
          objProps = getAllKeys(object),
          objLength = objProps.length,
          othProps = getAllKeys(other),
          othLength = othProps.length;

      if (objLength != othLength && !isPartial) {
        return false;
      }
      var index = objLength;
      while (index--) {
        var key = objProps[index];
        if (!(isPartial ? key in other : hasOwnProperty.call(other, key))) {
          return false;
        }
      }
      // Assume cyclic values are equal.
      var stacked = stack.get(object);
      if (stacked && stack.get(other)) {
        return stacked == other;
      }
      var result = true;
      stack.set(object, other);
      stack.set(other, object);

      var skipCtor = isPartial;
      while (++index < objLength) {
        key = objProps[index];
        var objValue = object[key],
            othValue = other[key];

        if (customizer) {
          var compared = isPartial
            ? customizer(othValue, objValue, key, other, object, stack)
            : customizer(objValue, othValue, key, object, other, stack);
        }
        // Recursively compare objects (susceptible to call stack limits).
        if (!(compared === undefined
              ? (objValue === othValue || equalFunc(objValue, othValue, bitmask, customizer, stack))
              : compared
            )) {
          result = false;
          break;
        }
        skipCtor || (skipCtor = key == 'constructor');
      }
      if (result && !skipCtor) {
        var objCtor = object.constructor,
            othCtor = other.constructor;

        // Non `Object` object instances with different constructors are not equal.
        if (objCtor != othCtor &&
            ('constructor' in object && 'constructor' in other) &&
            !(typeof objCtor == 'function' && objCtor instanceof objCtor &&
              typeof othCtor == 'function' && othCtor instanceof othCtor)) {
          result = false;
        }
      }
      stack['delete'](object);
      stack['delete'](other);
      return result;
    }

如果是個(gè)普通對(duì)象,則調(diào)用Object.keys,本身不會(huì)返回原型鏈上的屬性
先判斷了長(zhǎng)度
然后循環(huán)遍歷通過(guò)hasOwnProperty 判斷在前者有的后者自身屬性上也有,而非繼承而來(lái)
然后循環(huán)比較值,通過(guò)(objValue === othValue || equalFunc(objValue, othValue, bitmask, customizer, stack)) 比較深層次的值

總結(jié)

最大的收獲是Symbol.toStringTag,由于技術(shù)迭代的原因,寫(xiě)法很陳舊,有一些本就是病垢,不值得借鑒,對(duì)于業(yè)務(wù)開(kāi)發(fā)來(lái)說(shuō),實(shí)現(xiàn)顯得笨重了些。

很多寫(xiě)法減少了if、else,一定程度上復(fù)用了代碼,使得邏輯緊湊,卻也使得清晰性和可讀性下降了??赡苓@就是一個(gè)庫(kù)的研發(fā)人員,和一個(gè)庫(kù)的使用人員視角的不同。

作為一個(gè)使用者,lodash是十分好用的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • pyspark.sql模塊 模塊上下文 Spark SQL和DataFrames的重要類(lèi): pyspark.sql...
    mpro閱讀 9,915評(píng)論 0 13
  • 1、標(biāo)識(shí)符 標(biāo)識(shí)符是指變量、函數(shù)、屬性的名字,或函數(shù)的參數(shù)。 格式規(guī)則: 第一個(gè)字符必須是一個(gè)字母、下劃線(xiàn)(_)或...
    風(fēng)中丶凌亂閱讀 1,749評(píng)論 0 0
  • 函數(shù)和對(duì)象 1、函數(shù) 1.1 函數(shù)概述 函數(shù)對(duì)于任何一門(mén)語(yǔ)言來(lái)說(shuō)都是核心的概念。通過(guò)函數(shù)可以封裝任意多條語(yǔ)句,而且...
    道無(wú)虛閱讀 4,947評(píng)論 0 5
  • V8 Debugger Protocol This document describes the message ...
    妖怪來(lái)了閱讀 1,848評(píng)論 0 1
  • 變量 聲明變量 命名變量區(qū)分大小寫(xiě)的語(yǔ)言第一個(gè)字符是字母或下劃線(xiàn)_,數(shù)字不能作為第一個(gè)字符字符必須是字母,數(shù)字或者...
    flyingtoparis閱讀 893評(píng)論 0 0

友情鏈接更多精彩內(nèi)容