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