文章出處

代碼取自于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>

 


文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()