_.isEqual源碼學習

_.isEqual源碼學習

做用

_.isEqual 比較二者的值是否相等。
_.isEqualWith(value, other, [customizer]),也是比較二者的值是否相等。它還接受一個customizer

咱們須要比較的類型有許多。 對象,數組,字符串,布爾,Date,正則,Symbool等。javascript

實現

入口isEqual

/**
 * Performs a deep comparison between two values to determine if they are
 * equivalent.
 * 
 * **Note:** This method supports comparing arrays, array buffers, booleans,
 * date objects, error objects, maps, numbers, `Object` objects, regexes,
 * sets, strings, symbols, and typed arrays. `Object` objects are compared
 * by their own, not inherited, enumerable properties. Functions and DOM
 * nodes are **not** supported.

 * var object = { 'a': 1 };
 * var other = { 'a': 1 };
 *
 * _.isEqual(object, other);
 * // => true
 *
 * object === other;
 * // => false
 */
function isEqual(value, other) {
  return baseIsEqual(value, other);
}

isEqual方法提供了一個很深的比較來決定兩個值是否相等。該方法支持array,array buffers,booleans,data objects,error objects,maps,numbers,等等等等java

對於稍複雜一點的對象,支持比較自身的屬性,可是不包括繼承和可枚舉的屬性。node

不支持函數和dom節點的比較。數組

baseIsEqual

/**

 * @param {Function} [customizer] The function to customize comparisons.
 * @param {boolean} [bitmask] The bitmask of comparison flags.
 *  The bitmask may be composed of the following flags:
 *     1 - Unordered comparison 無序的比較
 *     2 - Partial comparison  部分比較。
 * @param {Object} [stack] Tracks traversed `value` and `other` objects. 跟蹤 傳入的value和other
 * @returns {boolean} 是否相等
 */
function baseIsEqual(value, other, customizer, bitmask, stack) {
  if (value === other) {
    return true;
  }
  if (value == null || other == null || (!isObject(value) && !isObjectLike(other))) {
    return value !== value && other !== other; //NaN
  }
  return baseIsEqualDeep(value, other, baseIsEqual, customizer, bitmask, stack);
}

_.isEqual的基本實現,支持部分比較和跟蹤遍歷對象。app

看了幾個參數,有幾點須要注意
  • stack是如和作到追蹤傳入的參數的
  • Unordered comparison 和 Partial comparison 是如何做用的
  • customizer 是怎麼處理值比較的。
  • 不一樣類型判斷equal的方式。

baseIsEqualDeep

/**
 * A specialized version of `baseIsEqual` for arrays and objects which performs
 * deep comparisons and tracks traversed objects enabling objects with circular
 * references to be compared.
 *
 * @private
 * @param {Object} object The object to compare.
 * @param {Object} other The other object to compare.
 * @param {Function} equalFunc The function to determine equivalents of values.
 * @param {Function} [customizer] The function to customize comparisons.
 * @param {number} [bitmask] The bitmask of comparison flags. See `baseIsEqual`
 *  for more details.
 * @param {Object} [stack] Tracks traversed `object` and `other` objects.
 * @returns {boolean} Returns `true` if the objects are equivalent, else `false`.
 */
function baseIsEqualDeep(object, other, equalFunc, customizer, bitmask, stack) {
  var objIsArr = isArray(object),//數組?
      othIsArr = isArray(other),//數組
      objTag = arrayTag,//[object Array]
      othTag = arrayTag;//[object Array]

  if (!objIsArr) {
    objTag = getTag(object);
    objTag = objTag == argsTag ? objectTag : objTag;//  '[object Arguments]' ?
  }
  if (!othIsArr) {
    othTag = getTag(other);
    othTag = othTag == argsTag ? objectTag : othTag;
  }
  var objIsObj = objTag == objectTag && !isHostObject(object),// obj是不是obj
      othIsObj = othTag == objectTag && !isHostObject(other),// oth是不是obj
      isSameTag = objTag == othTag; // obj 和 oth是不是統一類型

  if (isSameTag && !objIsObj) {
    stack || (stack = new Stack);
    return (objIsArr || isTypedArray(object))
      ? equalArrays(object, other, equalFunc, customizer, bitmask, stack)// 數組比較
      : equalByTag(object, other, objTag, equalFunc, customizer, bitmask, stack);// 對象比較
  }
  if (!(bitmask & PARTIAL_COMPARE_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, customizer, bitmask, stack);
    }
  }
  if (!isSameTag) { // 不是相同的標籤
    return false;
  }
  stack || (stack = new Stack);
  return equalObjects(object, other, equalFunc, customizer, bitmask, stack);
}
  • 針對數組。有equalArrays
  • 針對對象。有equalObjects
  • 針對otherTags有equa
  • 還有一個equalFunc,暫時不解釋

相關調用邏輯dom

  • 前置若是兩者不是相同的tag,直接return false
  • 若是相同的tag,並且是數組,調用equalArrays
  • 相同的tag,不是對象tag,不是數組,調用equalByTag
  • 相同的tag,是對象的話,調用equalObjects

equalByTag

baseIsEqualDeep的專門版本用來比較相同tag的對象。它僅支持Boolean`, `Date`, `Error`, `Number`, `RegExp`, or `String函數

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

    case arrayBufferTag: // '[object ArrayBuffer]'
      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. 布爾值會轉成"1"或者"0";日期會被轉換成毫秒。
      // Invalid dates are coerced to `NaN`.無效的dates會被轉成NaN
      return eq(+object, +other);// eq是淺比較判斷相等

    case errorTag: // => new Error
      return object.name == other.name && object.message == other.message;

    case regexpTag: // 正則 => new Rexg
    case stringTag: // 字符串 => new String
      // Coerce regexes to strings and treat strings, primitives and objects, 將regexes強制轉換爲字符串,若是轉換的字符串相等,則原對象也相等。
      // 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; // 將map轉成數組

    case setTag:
      var isPartial = bitmask & PARTIAL_COMPARE_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 |= UNORDERED_COMPARE_FLAG;

      // Recursively compare objects (susceptible to call stack limits).
      stack.set(object, other);
      // 調用的是數組比較方法,object和other爲set或者map,將之轉換成數組。再去比較。
      var result = equalArrays(convert(object), convert(other), equalFunc, customizer, bitmask, stack);
      stack['delete'](object);// 刪除掉對應的object
      return result;

    case symbolTag:
      if (symbolValueOf) { //symbolProto=>symbolProto.valueOf
        return symbolValueOf.call(object) == symbolValueOf.call(other);  // 相等條件是比較二者的valueof。
      }
  }
  return false;
}
  • 布爾,date,NumberTag,經過+value轉換成0或者1來比較。
  • error,比較條件是 error.name相等 && error.message相等
  • 正則和字符串, 對於正則,先轉換成字符串,再比較相等否。
  • map或者set,將object和other轉換成數組,再調用equalArrays判斷

equalObjects

一個特殊版本的baseIsEqualDeep方法,用來比較對象。學習

/**
 * A specialized version of `baseIsEqualDeep` for objects with support for
 * partial deep comparisons
 */
function equalObjects(object, other, equalFunc, customizer, bitmask, stack) {
  var isPartial = bitmask & PARTIAL_COMPARE_FLAG,
      objProps = keys(object),// object.keys(object) => [keyName0]
      objLength = objProps.length, // obj長度
      othProps = keys(other),//other keys數組
      othLength = othProps.length;// other的長度

  if (objLength != othLength && !isPartial) {
    return false;
  }
  var index = objLength;
  while (index--) { // 這裏判斷,若是 object的屬性名在other中不存在,直接返回
    var key = objProps[index];// obj中的屬性名
    if (!(isPartial ? key in other : /* other中有這個keyname**/hasOwnProperty.call(other, key))) { //object自由屬性中沒有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).
    // 這裏是判斷對應key的value是否相等。直接返回false。固然即使相等,這裏的邏輯也不足夠嚴謹,還要往下走,比對constructor。這裏使用`equalFunc`比較,是由於咱們沒法判斷value的類型,此時的equalFunc 還是`baseIsEqual`
    if (!(compared === undefined
          ? (objValue === othValue || equalFunc(objValue, othValue, customizer, bitmask, stack))
          : compared
        )) {
      result = false;
      break;
    }
    skipCtor || (skipCtor = key == 'constructor');
  }
  if (result && !skipCtor) { // result爲true,是經過上邊的比較以後,仍然相等,那麼就要去比對對象的constructor。即使key和value相等,可是constructor不相同,那麼仍然不相等。
    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;
}
  • equalObjects先去判斷objectother的屬性名是否相同,不相同直接返回。
  • 再去判斷對應key的value的值是否相等
  • 再若是仍然符合條件再去判斷constructor是否相同。

知足以上3個條件,對象纔是相等的。ui

equalArrays(數組比較方法)

/**
 * A specialized version of `baseIsEqualDeep` for arrays with support for
 * partial deep comparisons.
 */
function equalArrays(array, other, equalFunc, customizer, bitmask, stack) {
  var isPartial = bitmask & PARTIAL_COMPARE_FLAG,
      arrLength = array.length,//array的長度
      othLength = other.length;// other的長度

  if (arrLength != othLength && !(isPartial && othLength > arrLength)) { // 數組長度不相等,直接返回false
    return false;
  }
  // Assume cyclic values are equal. 假設循環值相等。

  var stacked = stack.get(array); // 若是stacked不是undefined,咱們當前看的 _.isEqual 是不存在這個判斷的
  if (stacked && stack.get(other)) {
    return stacked == other;
  }
  var index = -1,
      result = true,
      seen = (bitmask & UNORDERED_COMPARE_FLAG) ? new SetCache : undefined;

  stack.set(array, other); // array 和 other作了關聯 ,
  stack.set(other, array);

  // Ignore non-index properties. 忽略沒有index的屬性
  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 (!seen.has(othIndex) &&
                (arrValue === othValue || equalFunc(arrValue, othValue, customizer, bitmask, stack))) {
              return seen.add(othIndex);
            }
          })) {
        result = false;
        break;
      }
    } else if (!(
          arrValue === othValue ||
            equalFunc(arrValue, othValue, customizer, bitmask, stack) // 若是不相等,直接跳走。
        )) {
      result = false;
      break;
    }
  }
  stack['delete'](array);
  stack['delete'](other);
  return result;
}

TODO

  • stack的原理,在整個工做流程中作了哪些事情
  • baseIsEqual的bitmask參數是作什麼的
bitmask 按位操做符. 1&0=>0,undefined &1 =>0,1&1=>1 https://developer.mozilla.org...
相關文章
相關標籤/搜索