「我不知道的 JS」 相等性判断 🔥🔥🔥🔥

引言

在上文梳理 Map 的过程中, 在键值相等性比较一节遇到 零值相等 概念, 当时对这个概念那是一知半解, 所以特地针对 相等性判断 相关知识做了一次详细的梳理

一、严格相等「===」

1.1 简述

严格相等运算符 === 会检查它的两个操作数是否相等, 返回一个布尔值; 需要注意的是: 严格相等运算符总是认为不同类型的操作数是不同的

1.2 规则 & 演示

  1. 操作数的类型不同, 返回 false
'1' === 1 // false
1 === 1 // true
  1. 操作数都是对象, 只有当它们指向同一个对象时才返回 true, 因为比较的是引用地址
const obj = {}
const obc = obj
obj === obc // true

const o = {}
const b = {}
o === b // false
  1. 字符串类型必须拥有相同顺序的相同字符
'123' === '321' // false
'123' === '123' // true
  1. 布尔运算符必须同时为 true 或同时为 false
true === true // true
true === false // false
  1. 操作数都为 null 或都为 undefined, 返回 true
null === null // true
undefined === undefined // true
  1. 操作数有任意一个为 NaN, 返回 false
NaN === '1' // false
NaN === NaN // false
  1. 数字类型必须拥有相同的数值, 同时需要注意: +0-0 会被认为是相同的值
1 === 1 // true
0 === -0 // true

二、相等「==」

2.1 简述

  1. 相等运算符 == 检查其两个操作数是否相等, 返回一个布尔值结果
  2. 需要注意的是, 与 === 不同的是, == 在比较前会将两个被比较的值转换为相同类型后再使用 === 的规则进行比较
  3. 也就是说 == 是存在 隐式类型转换

2.2 规则 & 演示

下图演示的是使用 == 来对各种类型的操作数进行比较时, 对应的运行结果以及数据转换逻辑:

  • ToNumber(A) 表示在比较前将参数 A 转换为数字
  • ToPrimitive(A) 表示在比较前尝试调用 A.toString()A.valueOf() 方法, 获取到参数 A 的原始值
  • IsFalsy(A) 当且仅当 Aundefined 时返回 true

「我不知道的 JS」 相等性判断 🔥🔥🔥🔥

  1. 相同类型的比较, 将遵循 === 规则进行比较
1 == 1; // true
"hello" == "hello"; // true
NaN === NaN // false
0 === -0 // true
  1. 有类型转换的比较
  • 类型是 布尔值字符串数字 进行比较会优先转换为 数字, 进行比较
"1" == 1; // true
1 == "1"; // true
0 == false; // true
  • 字符串布尔值 对比, 会统一转为 数字 进行比较
'0' == true; // false
'1231' == true; // false
'ada' == true; // false
'1' == true; // true
  • nullundefined 相等, 和其他任何类型数据对比都是 false
null == undefined; // true
0 == null; // false
0 == undefined; // false
false == null; // false

== 运算中, 会尽可能将 基本类型 数据转为 数字 再进行比较

  1. 对象
  • 两个操作数都是对象, 按照 === 进行比较, 比较的将是对象的引用地址
const object1 = {};
const object2 = {};

console.log(object1 == object2); // false
console.log(object1 == object1); // true
  • 如果两个操作数有且仅有一个是对象, 则会先调用对象的 toString()valueOf() 获取到对象的原始值再进行比较
const a = [1, 2, 3];
const b = "1,2,3";
a == b; // true, a 调用了 toString() 方法, 再和 b 进行比较
a.toString() === b; // true


const string1 = "hello"; // 字符串类型
const string2 = String("hello"); // 字符串类型
const string3 = new String("hello"); // 对象
const string4 = new String("hello"); // 对象

console.log(string1 == string2); // true
console.log(string1 == string3); // true
console.log(string2 == string3); // true
console.log(string4 == string4); // true
console.log(string3 == string4); // false

三、同值相等 「Object.is()」

3.1 简述

JS 中 Object.is() 方法可以用于判断两个值是否为同一个值, 该方法判断逻辑和 === 区别如下:

  1. Object.is() 中认为不同的 NaN 是同一个值
  2. Object.is() 中认为 +0-0 是不同的值
  3. 其余判断逻辑和 === 保持一致
NaN === NaN; // false
Object.is(NaN, NaN); // true

0 === -0; // true
Object.is(0, -0); // false

3.2 对象中「不可变属性」相等性判断

当我们为对象设置了一个不可变属性, 后面如果试图修改该属性值, 将使用 同值相等 算法来判断修改前后的值是否是同一个, 如果不是将会抛出错误

const obj = {}

Object.defineProperty(
  obj, 
  "NaN", 
  { 
    value: NaN, 
    writable: false, 
    enumerable: false,
    configurable: false, 
  }
);

// 同值相等算法认为不同 NaN 是同一个值, 所以这里不会报错
Object.defineProperty(obj, "NaN", { value: NaN });

Object.defineProperty(
  obj, 
  "Number", 
  { 
    value: 0, 
    writable: false, 
    enumerable: false,
    configurable: false, 
  }
);

// 同值相等算法认为不同 0 和 -0 不是同一个值, 所以这里将会报错
Object.defineProperty(obj, "Number", { value: -0 });

3.3 Polyfill: 手撸一个 「Object.is()」

如下代码演示了如何自己实现一个 Object.is(), 其中关键判断有两点

  1. JS 中我们可以通过 x !== x 判断 x 是否是 NaN
  2. 我们可以通过 1 / x === 1 / y 来确保 x y 都是 0 或者 -0, 因为 1 / 0 === Infinity 1 / -0 === -Infinity
if (!Object.is) {
  Object.defineProperty(Object, "is", {
    value: function (x, y) {
      if (x === y) {
        // x y 不能 0 || 都等于 0 或者 -0
        return x !== 0 || 1 / x === 1 / y;
      } else {
        // 处理 NaN
        return x !== x && y !== y;
      }
    }
  });
}

四、零值相等

同值相等 唯一不同的是, 零值相等 认为 0-0 是相等的, JS同值相等 是由 Object.is() 提供, 但是 零值相等 是没有提供任何直接判断的方法的

MapSet 中对于键、值的比较就是使用 零值相等 算法来进行比较的:

const map = new Map()
map.set(0, '0')
map.set(NaN, '1')
map.has(-0) // true
map.has(NaN) // true

const set = new Set()
set.add(0)
set.add(NaN)
set.has(-0) // true
set.has(NaN) // true

五、总结

5.1 严格相等「===」

  1. 对比类型、对比值
  2. NaN 认为是不同的值
  3. 0-0 认为是相同的值

5.2 相等「==」

  1. 不同类型会先进行转换, 然后使用 === 规则进行对比
  2. 会尽可能将基本类型数据转为数字再进行比较, 所以 '1231' == true'ada' == true 都为 false
  3. nullundefined 相等, 同时他们和其他任何值都不等

5.3 同值相等

  1. NaN 被认为是相等的
  2. 0-0 被认为是不等的
  3. 其他规则同 ===
  4. JS 中由 Object.is() 方法提供该算法

5.4 零值相等

  1. 和同值相等唯一区别是, 在这里 0-0 被认为是相等的
  2. === 唯一区别是, 认为 NaN 是相等的
  3. 目前该算法没有暴露出对应的接口, 只是在 Map Set 中使用了该算法

5.5 对比图

下图是 == === Object.is() 在不同类型数据下的结果

「我不知道的 JS」 相等性判断 🔥🔥🔥🔥

六、参考

本文正在参加「金石计划」

原文链接:https://juejin.cn/post/7217459018238705724 作者:墨渊君

(0)
上一篇 2023年4月3日 上午10:46
下一篇 2023年4月3日 上午10:56

相关推荐

发表回复

登录后才能评论