var deepEq = function (a, b, aStack, bStack) { // Unwrap any wrapped objects. //如果a,b是_的一个实例的话,需要先把他们解包出来再进行比较。 if (a instanceof _) a = a._wrapped; if (b instanceof _) b = b._wrapped; // Compare `[[Class]]` names. //先根据a,b的Class字符串进行比较,如果两个对象的Class字符串都不一样, //那么直接可以认为两者不相等。 var className = toString.call(a); if (className !== toString.call(b)) return false; //如果两者的Class字符串相等,再进一步进行比较。 //优先检测内置对象之间的比较,非内置对象再往后检测。 switch (className) { // Strings, numbers, regular expressions, dates, and booleans are compared by value. //如果a,b为正则表达式,那么转化为字符串判断是否相等即可。 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")`. //如果a, b是字符串对象,那么转化为字符串进行比较。因为一下两个变量: //var x = new String('12'); //var y = new String('12'); //x === y是false,x === y也是false,但是我们应该认为x与y是相等的。 //所以我们需要将其转化为字符串进行比较。 return '' + a === '' + b; case '[object Number]': //数字对象转化为数字进行比较,并且要考虑new Number(NaN) === new Number(NaN)应该要成立的情况。 // `NaN`s are equivalent, but non-reflexive. // Object(NaN) is equivalent to NaN. if (+a !== +a) return +b !== +b; // An `egal` comparison is performed for other numeric values. //排除0 === -0 的情况。 return +a === 0 ? 1 / +a === 1 / b : +a === +b; case '[object Date]': //Date类型以及Boolean类型都可以转换为number类型进行比较。 //在变量前加一个加号“+”,可以强制转换为数值型。 //在Date型变量前加一个加号“+”可以将Date转化为毫秒形式;Boolean类型同上(转换为0或者1)。 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; case '[object Symbol]': return SymbolProto.valueOf.call(a) === SymbolProto.valueOf.call(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; 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. //初次调用eq函数时,aStack以及bStack均未被传递,在循环递归的时候,会被传递进来。 //aStack和bStack存在的意义在于循环引用对象之间的比较。 aStack = aStack || []; bStack = bStack || []; var length = aStack.length; while (length--) { // Linear search. Performance is inversely proportional to the number of // unique nested structures. if (aStack[length] === a) return bStack[length] === b; } // Add the first object to the stack of traversed objects. //初次调用eq函数时,就把两个参数放入到参数堆栈中去,保存起来方便递归调用时使用。 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; //长度不等,直接返回false认定为数组不相等。 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 = _.keys(a), key; length = keys.length; // Ensure that both objects contain the same number of properties before comparing deep equality. //对比属性数量,如果数量不等,直接返回false。 if (_.keys(b).length !== length) return false; while (length--) { // Deep compare each member key = keys[length]; if (!(_.has(b, key) && eq(a[key], b[key], aStack, bStack))) return false; } } // Remove the first object from the stack of traversed objects. //循环递归结束,把a,b堆栈中的元素推出。 aStack.pop(); bStack.pop(); return true; };
对于源码的解读我已经作为注释写在了源码中。 那么根据源码,可以将其逻辑抽象出来: