如何严格比较两个对象 a,b 是否完全相同
var a = {nane: "yang", age: 88, hobbies: ["music", "food", "sleep"] };
var b = {nane: "yang", age: 88, hobbies: ["music", "food", "sleep"] };
var c = b;
a == b;
// ==>false;
b == c;
// ==>true;
a与 b我们可以认为他们相等, 但是用== 或 === 比较的时候并不相等, 原因是 == 比较的是引用对象的内存地址, 所以如果想判断两个对象的内容信息是否相同, 需要遍历每个元素的值, 如果这个元素是一个对象或数组我们应该继续递归遍历下去, 直到我们比较的是相同引用的对象或基本类型值(数字, 字符串, bool, 正则)
1. a === b 为true情况小是否认为a, b一定相同?
只有一种特殊情况
0 === -0, 他们是相等的, 但是+0和-0在一些特殊情况的数学运算中是有很大的意义
1/0 //==>Infinity, 正无穷大
1/-0 //==>-Infinity, 负无穷大
所以 0 === -0
为true, 但是它们并不完全相同
参考 Harmony egal
proposal
所以除了0, 用===判断为真的a和b是相同的两个对象
if ( a === b ) return a !== 0 || 1 / a = 1 / b;
2. null 或 undefined
a,b 同时为null 或 undefined 才认为他们完全相同
if (a == null || b == null) return a === b;
3. NaN
在js中有个奇葩就是 NaN, 它不等于自身, 但是如果a, b都是NaN我们认为他们完全相同
if (a !== a) return b !== b;
4. 基本类型
如何区分一个变量是正则
,字符串
,数字
,日期
,bool
,数组
,函数
还是普通对象
类型?
typeof 与 Object.prototype.toString.call
typeof只能简单地区分基本类型还是对象, toString就完全能分辨出各个类型
var reg = /reg/,
str = "str",
number = 12,
date = new Date(),
bool = true,
arr = [2,3,4],
func = function(){},
obj = {name:"yang", age:99};
typeof reg; //==> object
Object.prototype.toString.call( reg ); //==> [object RegExp]
typeof str; //==> string
Object.prototype.toString.call( str ); //==> [object String]
typeof number; //==> number
Object.prototype.toString.call( number ); //==> [object Number]
typeof date; //==> object
Object.prototype.toString.call( date ); //==> [object Date]
typeof bool; //==> boolean
Object.prototype.toString.call( bool ); //==> [object String]
typeof arr; //==> object
Object.prototype.toString.call( arr ); //==> [object Array]
typeof func; //==> function
Object.prototype.toString.call( func ); //==> [object Function]
typeof obj; //==> object
Object.prototype.toString.call( obj ); //==> [object Object]
4.1正则和字符串
正则式一种特殊的对象, 可以转换成字符串进行比较
case '[object RegExp]':
case '[object String]':
return '' + a === '' + b;
4.2数字
case '[object Number]':
if (+a !== +a) return +b !== +b; //NaN情况
return +a === 0 ? 1 / +a === 1 / b : +a === +b;
4.3日期和bool
case '[object Date]':
case '[object Boolean]':
return +a === +b; //转化成数字比较
5.函数
两个不同的函数不能比较, 故认为他们不能相等, 但是如果a和b引用的是同一个函数那么a和b完全相等, 这种情况在第1点就已经判断完成了,因为 a===b
6. 数组和对象
首先可以简单地判断数组的长度 或者 对象属性的个数, 如果长度都不相等, a和b也不可能相同。
我们需要比较数组元素或对象的[key:value]是否对应相等, 如果是基本类型那么按照基本类型的比较方式进行比较, 如果是对象或者数组, 那么需要继续递归下去, 直到比较的内容是基本类型!
附源码: https://github.com/mmmy/underscore
// Internal recursive comparison function for `isEqual`.
// 内部函数, 递归比较, 辅助isEuqual
var eq, deepEq;
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 ,但是它们不完全相同
// 请参考[Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal).
if (a === b) return a !== 0 || 1 / a === 1 / b;
// A strict comparison is necessary because `null == undefined`.
// 全等, 因为null == undefiend
if (a == null || b == null) return a === b;
// `NaN`s are equivalent, but non-reflexive.
// NaN是相同的, 但是却不等于自身, 奇葩
if (a !== a) return b !== b;
// Exhaust primitive checks
// 先粗略判断
var type = typeof a;
if (type !== 'function' && type !== 'object' && typeof b != 'object') return false;
return deepEq(a, b, aStack, bStack);
};
// Internal recursive comparison function for `isEqual`.
// 内部函数, 递归比较, 辅助isEuqual
deepEq = function(a, b, aStack, bStack) {
// 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;
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]':
// Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
// equivalent to `new String("5")`.
// 原始类型和对应的包装类型 是 相等的; 所以"5"和new String("5")是相等的
return '' + a === '' + b;
case '[object Number]':
// `NaN`s are equivalent, but non-reflexive.
// Object(NaN) is equivalent to NaN
// NaN 是相等, 但是却不等于自身, 奇葩, Object(NaN)和NaN相等
if (+a !== +a) return +b !== +b;
// An `egal` comparison is performed for other numeric values.
// egal法比较数字是否相等
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.
// 日期和布尔 强制转换成数字类型来比较, 日期比较的是毫秒形式, 注意不合法的日期转换成数字后是NaN
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;
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`.
// 判定循环结构的相等性.
// 这个测试循环结构的算法适用于 ES 5.1 第 15.12.3节, 抽象操作`J0`
// Initializing stack of traversed objects.
// It's done here since we only need them for objects and arrays comparison.
// 初始化stack
// 用于对象,数组的比较
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.
// 增加第一个对象到栈中
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 = _.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) && 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;
};
// Perform a deep comparison to check if two objects are equal.
// 深度比较两个对象是否相等
_.isEqual = function(a, b) {
return eq(a, b);
};