判断两个对象是否相等(_.isEqual源码逐行解析)

779 阅读2分钟

判断两个对象是否相等需要考虑到以下几点

  • -0 和 +0 的相等
  • NaN 和 NaN 的相等
  • 字符串 和 new String(字符串) 的相等
  • 构造函数的问题
  • 循环引用的问题

var eq = function(a, b, aStack, bStack) {
     // 为了应对可能出现 +0 === -0 的情况
     // 也可使用 Object.is(a, b)
    if (a === b) return a !== 0 || 1 / a === 1 / b;
    
    // 如果a和b有任意一个为null 直接return,为了尽早退出函数
    if (a == null || b == null) return a === b;
    
    // 通过toString的方式获取a的类型
    var className = toString.call(a);
    // 检查toString后 a和b是否相等
    if (className !== toString.call(b)) return fasle;
    switch(className) {
        // 这里的两项巧妙的运用了JS的隐式类型转换
        case '[object RegExp]': 
        case '[object String]':
            return '' + a === '' + b;
        // 判断 Number(NaN) Object(NaN) 的情况
        case '[object Number]':
            if (+a !== +a) return +b !== +b;
            return +a === 0 ? 1 / +a === 1 / b : +a === +b;
        // 这里的两项巧妙的运用了JS的隐式类型转换,boolean和Date可以被转为number类型在做判断
        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;
        // aCtor 和 bCtor 必须都存在并且都不是 Object 构造函数的情况下,aCtor 不等于 bCtor, 那这两个对象就真的不相等
        var aCtor = a.constructor, bCtor = b.constructor;
        if (aCtor !== bCtor && !((typeof aCtor === 'function') && aCtor instanceof aCtor && (typeof bCtor === 'function') && bCtor instanceof bCtor) && ('constructor' in a && 'constructor' in b)) {
            return false
        }
    }
    
    // 处理数组之前先考虑一下循环嵌套的问题
    // aStack和bStack用来存储递归比较过程中a和b的值
    aStack = aStack || [];
    bStack = bStack || [];
    var length = aStack.length;
    while(length--) {
        if (aStack[length] === a) return bStack[length] === b
    }
    aStack.push(a);
    bStack.push(b);
    
    // 再次递归处理
    if (areArrays) {
        length = a.length;
        if (length !== b.length) return false
        while(length--) {
            if (!eq(a[length], b[length], aStack, bStack)) return false
        }
    } else {
        var keys = _.keys(a), key;
        length = keys.length;
        if (_.keys(b).length !== length) return false;
        while(length--) {
            key = keys[length];
            if (!(_.has(b, key) && eq(a[key], b[key], aStack, bStack))) return false;
        }
    }
    
    aStack.pop();
    bStack.pop();
    return true;
}

_.isEqual = function(a,b) {
    return eq(a, b)
}