javascript之判断两个数据相等

188 阅读3分钟

前言

两个数据相等

  • 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;

参考:github.com/mqyqingfeng…