JavaScript 判断对象相等
作者:Seiya
时间:2019年08月26日
+0 和 -0
如果 a === b 的结果为 true, 那么 a 和 b 就是相等的吗?一般情况下,当然是这样的,但是有一个特殊的例子,就是 +0 和 -0。
// 表现1
console.log(+0 === -0); // true
// 表现2
(-0).toString() // '0'
(+0).toString() // '0'
// 表现3
-0 < +0 // false
+0 < -0 // false
// 表现4
1 / +0 // Infinity
1 / -0 // -Infinity
// 表现5
1 / +0 === 1 / -0 // false
那么我们可以这样进行判断:
function eq(a, b){
if (a === b) return a !== 0 || 1 / a === 1 / b;
return false;
}
console.log(eq(0, 0)) // true
console.log(eq(0, -0)) // false
NaN
在 JavaScript 中,NaN 和 NaN 是不相等的:
console.log(NaN === NaN); // false
那么,我们如何判断它们是否相等?这里可以利用 NaN 不等于自身的特性,我们可以区别出 NaN:
function eq(a, b) {
if (a !== a) return b !== b;
}
console.log(eq(NaN, NaN)); // true
这样,我们得到第一版代码:
// 用来过滤掉简单的类型比较,复杂的对象使用 deepEq 函数进行处理
function eq(a, b) {
if (a === b) return a !== 0 || 1 / a === 1 / b;
if (a !== a) return b !== b;
// typeof null 的结果为 object ,这里做判断,是为了让有 null 的情况尽早退出函数
if (a == null || b == null) return false;
// 判断参数 a 类型,如果是基本类型,在这里可以直接返回 false
var type = typeof a;
if (type !== 'function' && type !== 'object' && typeof b != 'object') return false;
return deepEq(a, b);
};
更多对象
我们可以利用隐式类型转换,判断更多对象类型:
String
:
console.log('Curly' + '' === new String('Curly') + ''); // true
Boolean
:
console.log(+true === +new Boolean(true)); // true
Number
:
console.log(+1 === +new Number(1)); // true
RegExp
:
console.log('' + /a/i === '' + new RegExp(/a/i)); // true
Date
:
console.log(+new Date(2009, 9, 25) === +new Date(2009, 9, 25)); // true
构造函数实例
我们来看一个例子:
function Person() {
this.name = name;
}
function Animal() {
this.name = name
}
var person = new Person('Kevin');
var animal = new Animal('Kevin');
eq(person, animal) // false
虽然 person 和 animal 都是 {name: 'Kevin'},但是 person 和 animal 属于不同构造函数的实例,为了做出区分,我们认为是不同的对象。
数组和对象
要进行数组和对象相等的判断,需要对数组和对象进行递归遍历,综上我们得到下面的代码:
var toString = Object.prototype.toString;
function deepEq(a, b) {
// a 和 b 的内部属性 [[class]] 相同时 返回 true
var className = toString.call(a);
if (className !== toString.call(b)) return false;
switch (className) {
case '[object RegExp]':
case '[object String]':
return '' + a === '' + b;
case '[object Number]':
if (+a !== +a) return +b !== +b;
return +a === 0 ? 1 / +a === 1 / b : +a === +b;
case '[object Date]':
case '[object Boolean]':
return +a === +b;
}
var areArrays = className === '[object Array]';
// 不是数组
if (!areArrays) {
// 过滤掉两个函数的情况
if (typeof a != 'object' || typeof b != 'object') return false;
var aCtor = a.constructor, bCtor = b.constructor;
// aCtor 和 bCtor 必须都存在并且都不是 Object 构造函数的情况下,aCtor 不等于 bCtor, 那这两个对象就真的不相等啦
if (aCtor !== bCtor && !(isFunction(aCtor) && aCtor instanceof aCtor && isFunction(bCtor) && bCtor instanceof bCtor) && ('constructor' in a && 'constructor' in b)) {
return false;
}
}
if (areArrays) {
let length = a.length;
if (length !== b.length) return false;
while (length--) {
if (!eq(a[length], b[length])) return false;
}
}
else {
var keys = Object.keys(a), key;
length = keys.length;
if (Object.keys(b).length !== length) return false;
while (length--) {
key = keys[length];
if (!(b.hasOwnProperty(key) && eq(a[key], b[key]))) return false;
}
}
return true;
}
注意:
关于循环引用的问题这里暂时不做讨论!