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

注意:

关于循环引用的问题这里暂时不做讨论!

最后更新时间: 2019-8-26 11:33:32