基本类型的两种特殊情况

  • 判断 +0 与 -0
  1. +0 === -0; // true

由于 js 表示数字使用的是 IEEE_754 浮点数表示法,最高位是符号位(0代表正,1代表负),则1000(-0)和0000(0)表示的都是0。所有才有正负区别。

  1. 1 / +0 === 1 / -0 // false
  • 判断 NaN
  1. NaN === NaN; // false

利用 NaN 与自身不相等的特性可以判断相等性。

  1. function eq(a, b) {
  2. if (a !== a) return b !== b;
  3. }

复杂类型

  • String

'' + a === '' + b

  • Boolean

+a === +b

  • Number

+a === +b ,需注意NaN这种特殊情况。

  • Date

+a === +b

  • RegExp

'' + a === '' + b

  • Function
    • '' + a === '' + b
    • 指向不同引用则认为不相等
  • Array
    • 递归遍历,需要注意循环引用的问题
  • Object
    • 递归遍历,同时也要注意循环引用的问题

总结:

  1. 利用隐式类型转换进行对比。
  2. 递归遍历。

参考实现

  1. var toString = Object.prototype.toString;
  2. function isFunction(obj) {
  3. return toString.call(obj) === '[object Function]'
  4. }
  5. function eq(a, b, aStack, bStack) {
  6. // === 结果为 true 的区别出 +0 和 -0
  7. if (a === b) return a !== 0 || 1 / a === 1 / b;
  8. // typeof null 的结果为 object ,这里做判断,是为了让有 null 的情况尽早退出函数
  9. if (a == null || b == null) return false;
  10. // 判断 NaN
  11. if (a !== a) return b !== b;
  12. // 判断参数 a 类型,如果是基本类型,在这里可以直接返回 false
  13. var type = typeof a;
  14. if (type !== 'function' && type !== 'object' && typeof b != 'object') return false;
  15. // 更复杂的对象使用 deepEq 函数进行深度比较
  16. return deepEq(a, b, aStack, bStack);
  17. };
  18. function deepEq(a, b, aStack, bStack) {
  19. // a 和 b 的内部属性 [[class]] 相同时 返回 true
  20. var className = toString.call(a);
  21. if (className !== toString.call(b)) return false;
  22. switch (className) {
  23. case '[object RegExp]':
  24. case '[object String]':
  25. return '' + a === '' + b;
  26. case '[object Number]':
  27. if (+a !== +a) return +b !== +b;
  28. return +a === 0 ? 1 / +a === 1 / b : +a === +b;
  29. case '[object Date]':
  30. case '[object Boolean]':
  31. return +a === +b;
  32. }
  33. var areArrays = className === '[object Array]';
  34. // 不是数组
  35. if (!areArrays) {
  36. // 过滤掉两个函数的情况
  37. if (typeof a != 'object' || typeof b != 'object') return false;
  38. var aCtor = a.constructor,
  39. bCtor = b.constructor;
  40. // aCtor 和 bCtor 必须都存在并且都不是 Object 构造函数的情况下,aCtor 不等于 bCtor, 那这两个对象就真的不相等啦
  41. if (aCtor !== bCtor && !(isFunction(aCtor) && aCtor instanceof aCtor && isFunction(bCtor) && bCtor instanceof bCtor) && ('constructor' in a && 'constructor' in b)) {
  42. return false;
  43. }
  44. }
  45. aStack = aStack || [];
  46. bStack = bStack || [];
  47. var length = aStack.length;
  48. // 检查是否有循环引用的部分
  49. while (length--) {
  50. if (aStack[length] === a) {
  51. return bStack[length] === b;
  52. }
  53. }
  54. aStack.push(a);
  55. bStack.push(b);
  56. // 数组判断
  57. if (areArrays) {
  58. length = a.length;
  59. if (length !== b.length) return false;
  60. while (length--) {
  61. if (!eq(a[length], b[length], aStack, bStack)) return false;
  62. }
  63. }
  64. // 对象判断
  65. else {
  66. var keys = Object.keys(a),
  67. key;
  68. length = keys.length;
  69. if (Object.keys(b).length !== length) return false;
  70. while (length--) {
  71. key = keys[length];
  72. if (!(b.hasOwnProperty(key) && eq(a[key], b[key], aStack, bStack))) return false;
  73. }
  74. }
  75. aStack.pop();
  76. bStack.pop();
  77. return true;
  78. }
  79. console.log(eq(0, 0)) // true
  80. console.log(eq(0, -0)) // false
  81. console.log(eq(NaN, NaN)); // true
  82. console.log(eq(Number(NaN), Number(NaN))); // true
  83. console.log(eq('Curly', new String('Curly'))); // true
  84. console.log(eq([1], [1])); // true
  85. console.log(eq({ value: 1 }, { value: 1 })); // true
  86. var a, b;
  87. a = { foo: { b: { foo: { c: { foo: null } } } } };
  88. b = { foo: { b: { foo: { c: { foo: null } } } } };
  89. a.foo.b.foo.c.foo = a;
  90. b.foo.b.foo.c.foo = b;
  91. console.log(eq(a, b)) // true

参考资料