js中两个对象的比较

代码取自于underscore.js 1.8.3的isEqual函数。

做了一些小小的修改,主要是Function的比较修改。

自己也加了一些代码解读。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>js中两个对象的比较</title>
    <script>
/*
需求难点描述:
数组和对象,都能包含自身,还能包含其它类型。
所以数组之间的比较,要递归。
所以这块代码的设计是:
不能包含自身的,先比较。
然后是数组,对象比较。
特别要注意的是,对象的循环引用。
递归时,要记录递归的路径。
*/    
function isEqual(a, b) {
  var toString = Object.prototype.toString,
    object_keys = Object.keys,
    has = function(obj, key) {
      return obj != null && hasOwnProperty.call(obj, key);
    };
    var isFunction = function(fn){
        return toString.call(fn) == '[object Function]' ? true : false;
    };    
  var eq = function(a, b, aStack, bStack) {
    // Identical objects are equal. `0 === -0`, but they aren't identical.
    // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal).
    // 验证0===-0
    if (a === b) return a !== 0 || 1 / a === 1 / b;
    // A strict comparison is necessary because `null == undefined`.
    // null
    // 验证null == undefined
    if (a == null || b == null) return a === b;

    // Compare `[[Class]]` names.
    var className = toString.call(a);
    if (className !== toString.call(b)) return false;
    switch (className) {
      // Strings, numbers, regular expressions, dates, and booleans are compared by value.
      case '[object RegExp]':
        // RegExps are coerced to strings for comparison (Note: '' + /a/i === '/a/i')
      case '[object String]':
        // function虽然是引用,但两个内容一样的funciton应该相等。
      case '[object Function]':
        // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
        // equivalent to `new String("5")`.
        return '' + a === '' + b;
      case '[object Number]':
        // `NaN`s are equivalent, but non-reflexive.
        // Object(NaN) is equivalent to NaN
        // 验证 NaN
        if (+a !== +a) return +b !== +b;
        // An `egal` comparison is performed for other numeric values.
        return +a === 0 ? 1 / +a === 1 / b : +a === +b;
      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.
        return +a === +b;
    }

    var areArrays = className === '[object Array]';
    if (!areArrays) {
      if (typeof a != 'object' || typeof b != 'object') return false;

      // Objects with different constructors are not equivalent, but `Object`s or `Array`s
      // from different frames are.
      var aCtor = a.constructor,
        bCtor = b.constructor;
        // 判断顺序
        // 构造器一致
        // 构造器为函数
        // 拥有构造器属性
        // Function instanceof Function == true
        // Object instanceof Object == true
        // Array instanceof Array == false
      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`.

    // Initializing stack of traversed objects.
    // It's done here since we only need them for objects and arrays comparison.
    aStack = aStack || [];
    bStack = bStack || [];
    var length = aStack.length;

    while (length--) {
      // 递归才会走到这步 
       // Linear search. Performance is inversely proportional to the number of
      // unique nested structures.
      // 检测循环引用,参考用例如下
      /*
      var a = {
        "str":"string",
      };
      a["test"]=a;

      var b = {
        "str":"string",
      };
      b["test"]=b;

      console.log (isEqual(a,b));
      */
      if (aStack[length] === a){
          // 判断b的引用是否也循环,跳出循环引用这个坑
        return bStack[length] === b;
      } 
    }

    // Add the first object to the stack of traversed objects.
    // 递归压栈
    aStack.push(a);
    bStack.push(b);

    // 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--) {
        if (!eq(a[length], b[length], aStack, bStack)) return false;
      }
    } else {
      // Deep compare objects.
      // 把对象的属性们转换成一个数组
      var keys = object_keys(a),
        key;
      length = keys.length;
      // Ensure that both objects contain the same number of properties before comparing deep equality.
      if (object_keys(b).length !== length) return false;
      while (length--) {
        // Deep compare each member
        key = keys[length];
        // b也有a一样的key,则递归
        if (!(has(b, key) && eq(a[key], b[key], aStack, bStack))) return false;
      }
    }
    // Remove the first object from the stack of traversed objects.
    // 递归出栈
    aStack.pop();
    bStack.pop();
    return true;
  };

  return eq(a,b);
}

var a = {
  "str":"ying",
};
a["test"]=a;

var b = {
  "str":"ying",
};
b["test"]=a;

console.log (isEqual(a,b));
    
    </script>
</head>
<body>
    
</body>
</html>

 

posted @ 2015-06-18 16:35  草珊瑚  阅读(3092)  评论(0编辑  收藏  举报