前言
两个数据相等
- NaN 和 NaN 是相等
- [1] 和 [1] 是相等
- {value: 1} 和 {value: 1} 是相等
不仅仅是这些长得一样的,还有
- 1 和 new Number(1) 是相等
- 'Curly' 和 new String('Curly') 是相等
- true 和 new Boolean(true) 是相等
-0 和 +0
如果 a === b 的结果为 true, 那么 a 和 b 就是相等的吗?一般情况下,当然是这样的,但是有一个特殊的例子,就是 +0 和 -0。
-0 == 0
+0 == 0
-0 == +0
(-0).toString(); //'0'
(+0).toString(); //'0'
-0 < +0 //false
+0 < -0 //false
即便如此,两者还是不同的
1/-0 //-Infinity
1/+0 //Infinity
1/-0 === 1/+0 //false
什么时候会生成-0
Math.rount(-0.5) //-0
Math.ceil(-0.9) //-0
那么我们又该如何在 === 结果为 true 的时候,区别 0 和 -0 得出正确的结果呢?
function equal(a, b){
if(a === b){
return a !== 0 || 1/a === 1/b;
}
return false;
}
NaN和NaN
NaN === NaN //false
那么我们又该如何在 === 结果为 false 的时候,区别 NaN 和 NaN 得出正确的结果呢?
function equal(a, b){
if(a !== a){
return b !== b;
}
}
基本类型和类对象
'Curly' === new String('Curly') //false
console.log(typeof 'Curly'); // string
console.log(typeof new String('Curly')); // object
所以不能使用typeof去判断,而可以使用Object.prototype.toString得到结果一样
Object.prototype.toString.call('Curly') //'[object String]'
Object.prototype.toString.call(new String('Curly') //'[object String]'
再加上数值一样就可以了,如下针对不同类型使得数值相等
'Curly' === new String('Curly') + ''
+true === +new Boolean(true)
+new Date(2019, 1, 10) === +new Date(2019, 1, 10)
+10 === +new Number(10)
/a/i + '' === new RegExp(/a/i) + ''
构造函数的实例
如下来自两个不同的构造函数,他们应该是不相等的
function A(name){
this.name = name;
}
function B(name){
this.name = name;
}
var a = new A();
var b = new B();
function equal(){
...
let aIsArrays = tagA === '[object Array]';
//不是数组,只能是对象类型或者函数类型
if(!aIsArrays){
// 过滤掉两个函数的情况
if (typeA != 'object' || typeB != 'object') return false;
//对于不同构造函数下的实例直接返回 false
let 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;
}
}
...
}
最后汇总
function equal(a, b, aStack, bStack){
//-0和+0
if(a === b){
return a !== 0 || 1/a === 1/b;
}
//NaN和NaN
if(a !== a){
return b !== b;
}
// null和其他
if (a == null || b == null) {
return null;
}
//如果a是基本类型,b必须是引用类型
let typeA = typeof a;
let typeB = typeof b;
if((typeA !== 'function' && typeA !== 'object') && typeB !== 'object'){
return false;
}
//如果b是基本类型,a必须是引用类型
if ((typeB !== 'function' && typeB !== 'object') && typeA !== 'object') {
return false;
}
let tagA = toString.call(a);
if(tagA !== toString.call(b)){
return false;
}
//1和 new Number(1)
switch (tagA) {
case '[object Number]':
case '[object Date]':
return +a === +b;
case '[object String]':
case '[object RegExp]':
return a + '' === b + '';
case '[object Number]':
if (+a !== +a) return +b !== +b;
return +a === 0 ? 1 / +a === 1 / b : +a === +b;
default:
break;
}
let aIsArrays = tagA === '[object Array]';
//不是数组,只能是对象类型或者函数类型
if(!aIsArrays){
// 过滤掉两个函数的情况
if (typeA != 'object' || typeB != 'object') return false;
let 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;
}
}
//循环引用
aStack = aStack || [];
bStack = bStack || [];
let stackLength = aStack.length;
while (stackLength--) {
if(aStack[stackLength] === a){
return bStack[stackLength] === b;
}
}
aStack.push(a);
bStack.push(b);
if(aIsArrays){//数组[1]和[1]
let length = a.length;
if(length !== b.length){
return false;
}
while (length--) {
if(!equal(a[length], b[length], aStack, bStack) ){
return false;
}
}
} else {//平面对象{value: 1} 和 {value: 1}
let keys = Object.keys(a),
key,
keysLength = keys.length;
if(keysLength !== Object.keys(b).length){
return false;
}
while (keysLength--) {
key = keys[keysLength];
if(!(b.hasOwnProperty(key) && equal(a[key], b[key], aStack, bStack))){
return false;
}
}
}
aStack.pop();
bStack.pop();
return true;
}
equal(0, -0) //false
equal(0, +0) //true
equal(NaN, NaN) //true
equal(new Number(NaN), NaN) //true
console.log(eq('Curly', new String('Curly'))); // true
console.log(eq([1], [1])); // true
console.log(eq({ value: 1 }, { value: 1 })); // true
var a, b;
a = { foo: { b: { foo: { c: { foo: null } } } } };
b = { foo: { b: { foo: { c: { foo: null } } } } };
a.foo.b.foo.c.foo = a;
b.foo.b.foo.c.foo = b;