• Jump To … +
    modules/_baseCreate.js modules/_baseIteratee.js modules/_cb.js modules/_chainResult.js modules/_collectNonEnumProps.js modules/_createAssigner.js modules/_createEscaper.js modules/_createIndexFinder.js modules/_createPredicateIndexFinder.js modules/_createReduce.js modules/_createSizePropertyCheck.js modules/_deepGet.js modules/_escapeMap.js modules/_executeBound.js modules/_flatten.js modules/_getByteLength.js modules/_getLength.js modules/_group.js modules/_has.js modules/_hasObjectTag.js modules/_isArrayLike.js modules/_isBufferLike.js modules/_keyInObj.js modules/_methodFingerprint.js modules/_optimizeCb.js modules/_setup.js modules/_shallowProperty.js modules/_stringTagBug.js modules/_tagTester.js modules/_toBufferView.js modules/_toPath.js modules/_unescapeMap.js modules/after.js modules/allKeys.js modules/before.js modules/bind.js modules/bindAll.js modules/chain.js modules/chunk.js modules/clone.js modules/compact.js modules/compose.js modules/constant.js modules/contains.js modules/countBy.js modules/create.js modules/debounce.js modules/defaults.js modules/defer.js modules/delay.js modules/difference.js modules/each.js modules/escape.js modules/every.js modules/extend.js modules/extendOwn.js modules/filter.js modules/find.js modules/findIndex.js modules/findKey.js modules/findLastIndex.js modules/findWhere.js modules/first.js modules/flatten.js modules/functions.js modules/get.js modules/groupBy.js modules/has.js modules/identity.js modules/index-all.js modules/index-default.js modules/index.js modules/indexBy.js modules/indexOf.js modules/initial.js modules/intersection.js modules/invert.js modules/invoke.js modules/isArguments.js modules/isArray.js modules/isArrayBuffer.js modules/isBoolean.js modules/isDataView.js modules/isDate.js modules/isElement.js modules/isEmpty.js modules/isEqual.js modules/isError.js modules/isFinite.js modules/isFunction.js modules/isMap.js modules/isMatch.js modules/isNaN.js modules/isNull.js modules/isNumber.js modules/isObject.js modules/isRegExp.js modules/isSet.js modules/isString.js modules/isSymbol.js modules/isTypedArray.js modules/isUndefined.js modules/isWeakMap.js modules/isWeakSet.js modules/iteratee.js modules/keys.js modules/last.js modules/lastIndexOf.js modules/map.js modules/mapObject.js modules/matcher.js modules/max.js modules/memoize.js modules/min.js modules/mixin.js modules/negate.js modules/noop.js modules/now.js modules/object.js modules/omit.js modules/once.js modules/pairs.js modules/partial.js modules/partition.js modules/pick.js modules/pluck.js modules/property.js modules/propertyOf.js modules/random.js modules/range.js modules/reduce.js modules/reduceRight.js modules/reject.js modules/rest.js modules/restArguments.js modules/result.js modules/sample.js modules/shuffle.js modules/size.js modules/some.js modules/sortBy.js modules/sortedIndex.js modules/tap.js modules/template.js modules/templateSettings.js modules/throttle.js modules/times.js modules/toArray.js modules/toPath.js modules/underscore-array-methods.js modules/underscore.js modules/unescape.js modules/union.js modules/uniq.js modules/uniqueId.js modules/unzip.js modules/values.js modules/where.js modules/without.js modules/wrap.js modules/zip.js
  • isEqual.js

  • ¶
    import _ from './underscore.js';
    import { toString, SymbolProto } from './_setup.js';
    import getByteLength from './_getByteLength.js';
    import isTypedArray from './isTypedArray.js';
    import isFunction from './isFunction.js';
    import { hasDataViewBug }  from './_stringTagBug.js';
    import isDataView from './isDataView.js';
    import keys from './keys.js';
    import has from './_has.js';
    import toBufferView from './_toBufferView.js';
  • ¶

    We use this string twice, so give it a name for minification.

    var tagDataView = '[object DataView]';
  • ¶

    Perform a deep comparison to check if two objects are equal.

    export default function isEqual(a, b) {
  • ¶

    Keep track of which pairs of values need to be compared. We will be trampolining on this stack instead of using function recursion. (CVE-2026-27601)

      var todo = [{a: a, b: b}];
  • ¶

    Initializing stacks of traversed objects for cycle detection.

      var aStack = [], bStack = [];
  • ¶

    Keep traversing pairs until there is nothing left to compare.

      while (todo.length) {
        var frame = todo.pop();
  • ¶

    As a special case, a single true on the todo means that we can unwind the cycle detection stacks.

        if (frame === true) {
  • ¶

    Remove the first object from the stack of traversed objects.

          aStack.pop();
          bStack.pop();
          continue;
        }
        a = frame.a;
        b = frame.b;
  • ¶

    Identical objects are equal. 0 === -0, but they aren’t identical. See the Harmony egal proposal.

        if (a === b) {
          if (a !== 0 || 1 / a === 1 / b) continue;
          return false;
        }
  • ¶

    null or undefined only equal to itself (strict comparison).

        if (a == null || b == null) return false;
  • ¶

    NaNs are equivalent, but non-reflexive.

        if (a !== a) {
          if (b !== b) continue;
          return false;
        }
  • ¶

    Exhaust primitive checks

        var type = typeof a;
        if (type !== 'function' && type !== 'object' && typeof b != 'object') return false;
  • ¶

    Unwrap any wrapped objects.

        if (a instanceof _) a = a._wrapped;
        if (b instanceof _) b = b._wrapped;
  • ¶

    Compare [[Class]] names.

        var className = toString.call(a);
        if (className !== toString.call(b)) return false;
  • ¶

    Work around a bug in IE 10 - Edge 13.

        if (hasDataViewBug && className == '[object Object]' && isDataView(a)) {
          if (!isDataView(b)) return false;
          className = tagDataView;
        }
        switch (className) {
  • ¶

    These types are compared by value.

        case '[object RegExp]':
  • ¶

    RegExps are coerced to strings for comparison (Note: ‘’ + /a/i === ‘/a/i’)

        case '[object String]':
  • ¶

    Primitives and their corresponding object wrappers are equivalent; thus, "5" is equivalent to new String("5").

          if ('' + a === '' + b) continue;
          return false;
        case '[object Number]':
          todo.push({a: +a, b: +b});
          continue;
        case '[object Date]':
        case '[object Boolean]':
  • ¶

    Coerce dates and booleans to numeric primitive values. Dates are compared by their millisecond representations. Note that invalid dates with millisecond representations of NaN are not equivalent.

          if (+a === +b) continue;
          return false;
        case '[object Symbol]':
          if (SymbolProto.valueOf.call(a) === SymbolProto.valueOf.call(b)) continue;
          return false;
        case '[object ArrayBuffer]':
        case tagDataView:
  • ¶

    Coerce to typed array so we can fall through.

          todo.push({a: toBufferView(a), b: toBufferView(b)});
          continue;
        }
    
        var areArrays = className === '[object Array]';
        if (!areArrays && isTypedArray(a)) {
          var byteLength = getByteLength(a);
          if (byteLength !== getByteLength(b)) return false;
          if (a.buffer === b.buffer && a.byteOffset === b.byteOffset) continue;
          areArrays = true;
        }
        if (!areArrays) {
          if (typeof a != 'object' || typeof b != 'object') return false;
  • ¶

    Objects with different constructors are not equivalent, but Objects or Arrays from different frames are.

          var aCtor = a.constructor, bCtor = b.constructor;
          if (aCtor !== bCtor && !(isFunction(aCtor) && aCtor instanceof aCtor &&
                                   isFunction(bCtor) && bCtor instanceof bCtor)
              && ('constructor' in a && 'constructor' in b)) {
            return false;
          }
        }
  • ¶

    Assume equality for cyclic structures. The algorithm for detecting cyclic structures is adapted from ES 5.1 section 15.12.3, abstract operation JO.

        var length = aStack.length;
        while (length--) {
  • ¶

    Linear search. Performance is inversely proportional to the number of unique nested structures.

          if (aStack[length] === a) {
  • ¶

    Cycle detected. Break out of the inner loop and continue the outer loop. Step 1:

            if (bStack[length] === b) break;
            return false;
          }
        }
  • ¶

    Step 2, use length to verify whether we detected a cycle:

        if (length >= 0) continue;
  • ¶

    Add the first object to the stack of traversed objects.

        aStack.push(a);
        bStack.push(b);
  • ¶

    Remember to remove them again after the recursion below.

        todo.push(true);
  • ¶

    Recursively compare objects and arrays.

        if (areArrays) {
  • ¶

    Compare array lengths to determine if a deep comparison is necessary.

          length = a.length;
          if (length !== b.length) return false;
  • ¶

    Deep compare the contents, ignoring non-numeric properties.

          while (length--) {
            todo.push({a: a[length], b: b[length]});
          }
        } else {
  • ¶

    Deep compare objects.

          var _keys = keys(a), key;
          length = _keys.length;
  • ¶

    Ensure that both objects contain the same number of properties before comparing deep equality.

          if (keys(b).length !== length) return false;
          while (length--) {
  • ¶

    Deep compare each member

            key = _keys[length];
            if (!has(b, key)) return false;
            todo.push({a: a[key], b: b[key]});
          }
        }
      }
  • ¶

    We made it to the end and found no differences.

      return true;
    }