判断数据是否相等(undescore eq 源码)

217 阅读2分钟

判断数据相等的情况:

  • NaN === NaN
  • 1 === 1 === new Number(1) but +0 !== -0
  • 'string' === 'string' === new String('string')
  • true === true === new Boolean(true)
  • {value: 1} === {value: 1}
  • null === null
  • undefined === undefined
  • 构造函数
  • 数组
  • 循环引用
function eq(a, b) {
  // 1 / +0 !== 1 /-0
  if (a === b) return a !== 0 || 1/a === 1/b;
  // NaN !== NaN
  if (a !== a) return b!==b;
  // typeof null === "object",尽早抛出 null 情况,为下面需要判断复杂情况排除例外的情况 
  if (a === null || b === null) return false;
  var type = typeof a;
  // a 是基础类型而b不是object类型时,直接返回false
  // typeof new Number(1) === "object" 
  if (type !== 'function' && type !== 'object' && typeof b !== 'object') return false;
  // astack, bstack 是为了判断是否存在循环引用的情况
  return deepEq(a, b, astack, bstack);
}

var toString = Object.prototype.toString;
function isFunction(obj) {
    return toString.call(obj) === '[object Function]'
}
function deepEq(a, b, astack, bstack) {
  var className = toString.call(a);
  // Object.prototype.toString.call(1) === Object.prototype.toString.call(new Number(1)); 
  // "[object, Number]"
  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;
  }
  // 判断构造函数实例
  // a,b 的数据类型有可能是 Array,Object 和 Function
  var areArrays = className === "[object Array]"
  if (!areArray) {
    // 过滤 Function 类型值,因为函数之间比较是不相等的。
    // 这样就只剩下 object 的类型值进行比较了
    if (typeof a !== 'object' || typeof b !== 'object') return false;
    var aCtor = a.constructor, bCtor = b.constructor;
    // 数据类型一致,但构造函数不一致的情况
    if (
      // 构造函数不一致的情况
    	aCtor !== bCtor &&
      // 排除 a, b 是 {} / new Object('') 的情况(猜测)
      // Object instanceof Object result is true
      // aCtor: [Function: Object]; aCtor instanceof aCtor is true;
      // 实践的时候,发现 当 a: {a: 1}, b: {a: 1} 时,aCtor !== bCtor
      // 所以这个判断存在的意义还不明确
      !(
        isFunction(aCtor) && aCtor instanceof aCtor && 
        isFunction(bCtor) && bCtor instanceof bCtor
       ) &&
      // a,b 都有构造函数
      ('constructor' in a && 'constructor' in b)
      return false;
    )
  }
  // 判断是否循环引用
  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) {
    // 如果长度不等,a,b不相等
    if (legnth !== b.length) return false;
    while (length--) {
      if (!eq(a[length], b[length], astack, bstack)) return false;
    }
  } else {
    // 判断对象
    var keys = Object.keys(a), key;
    // 如果长度不等,a,b不相等
    if (Object.keys(b).length !== length) return false;
    while (length--) {
      key = keys[length];
      if (!(b.hasOwnProperty(a[key) && eq(a[key], b[key], astack, bstack))) return false;
    }
  }
  // 出栈
  astack.pop();
  bstack.pop();
  return true;
}

常用的数据的比较使用 JSON.stringify(obj) 来做比较也可以,但是有几种情况是不足的:

  • 遇到循环引用,会报错
  • 正则会处理为对象,如 obj = {a: /test/i};JSON.stringify(obj);// result: "{"a": {}}"

参考:

underscore源码解读-eq

JavaScript专题之如何判断两个对象相等