搞懂 js 中的宽松比较 == 、严格比较=== 、 Object.is

1,065 阅读5分钟

前言

在语言中,使用比较来判断操作数是比较常见的行为

对于 js 这门弱语言来说, 比较主要有三种形式,分别是 ==(宽松比较),=== (严格相等)和 Object.js,特别是 ==(宽松比较) 中的类型转化常常令人迷惑,今天一文彻底搞懂

宽松比较 ==

== 应该是最为让人熟知的是会 「🔗自动类型转化」, 我们一般会在 字符串数字 或者 布尔 这些 基本类型 之间相互转化。

'1' == 1 // true
'1' == true // true
 1 == false // false

如果操作数一方有 对象 呢?他们的转化规则是什么呢?

1 == [1] // ?
{a:1} == "{a:1}" // ?

🔗MDN关于宽松比较中操作数中一方是对象 有这样的解释:

如果其中一个操作数是对象,另一个是基本类型,按此顺序使用对象的 @@toPrimitive()(以 "default" 作为提示),valueOf() 和 toString() 方法将对象转换为基本类型。(这个基本类型转换与相加中使用的转换相同。)

其中 🔗@@toPrimitivejs 内部方法,主要作用是用于 「返回对象原始类型的方法」

const object1 = {
  [Symbol.toPrimitive](hint) {
    if (hint === 'number') {
      return 42;
    }
    return null;
  },
};

console.log(+object1); // 42

📌没有 @@toPrimitive 属性的对象通过以不同的顺序调用 valueOf() 和 toString() 方法将其转换为原始值

toPrimitive 一般不会自己定义,但是 valueOftoString 方法一定是存在的,因为在object上是有默认方法的。

一般情况下,我们也是不会自定义 valueOftoString 方法的

那么常见对象默认的 valueOftoString 的返回值是什么呢?

Snipaste_2023-12-09_16-58-54.png

❤️对于对象来说,执行toString可以返回字符串 [object Object]
❤️对于数组来说,toString 返回的是表示 🔗数组元素组成的字符串,相当于调用arr.join 方法

所以,我们可以得到如下结果: 6.png


当然我们可以自定义 valueOftoString 方法,如果同时定义这两个方法,会 优先 调用 valueOf 方法,如果 valueOf 返回的不是一个基本类型,则会调用 toString 方法,如果 toString 返回的仍然不是 基本类型,会导致 🔗TypeError

let myObj = {
// valueof 返回数字1
 valueOf(){
  return 1
 },
 // toString 返回字符串2
 toString(){
  return '2'
 }
}

myObj == "2"  // false
// 执行了 valueOf
myObj == 1  // true

Snipaste_2023-12-09_16-50-37.png

let myObj = {
// valueof 返回引用类型
 valueOf(){
  return []
 },
 // toString 返回字符串2
 toString(){
  return '2'
 }
}

// 执行了 toString
myObj == "2"  // true

myObj == 1  // false

Snipaste_2023-12-09_11-12-52.png

根据 🔗MDN关于宽松比较 的定义,我进行了一个简单的总结

  • 特殊情况
    • null / undefined 他们虽然是不同类型,但是是相等的
    • NaN 与任何值相比都是 false,甚至与自己相比 都是 false

介绍完特殊情况,介绍一般情况

  1. 如果两个操作数 类型相同,执行严格比较
  2. 如果不相同,会执行 类型转化后 再次执行宽松比较,直到类型相同
    1. 如果其中有一个操作数是数值, 另一个操作数是字符串,则将字符串使用 Number方法 转化为相对应的数值,如果对字符串执行 Number 失败,会返回 NaN
    2. 如果一个操作数是 布尔值,则将布尔值 转换为数值
    3. 如果一个操作数是 对象,另一个操作数是 数值或者字符串,则将对象转化为原始值,然后执行步骤1或者步骤2

例子

  1. 一个操作数是数字,另一个是字符串
'123' == 123

会对 字符串 123 执行 Number 构造函数,得到 数字123,然后执行步骤1, 数字123 === 数字123,所以 '123' == 123 的结果为 true

  1. 一个操作数是布尔值
false == 0
false == '0'

当其中一个操作数是 布尔值 时,true 转换为 1,false 转换为 0
所以 false == 0 相当于 数字0 == 数字0,所以返回 true

当另一个操作数是字符串的时候,布尔值依然会转化
会转化为 数字0 == 字符'0',此时的情况又回到了例子1, 所以也是返回 true

  1. 一个操作数是对象
let x = {
 a:1
}

console.log(x == "[object Object]") // true

let x2 = [1,2,3]
// 相当于调用 join 方法
console.log(x2 == "1,2,3")

严格相等 === 与 Object.is

宽松相等 相比,🔗严格相等 要容易的多,因为 严格相等不进行类型转化,所以只需要判断两边操作数是否相等即可

Object.is 也不执行类型转化,那么它 与 === 的区别在哪呢?

主要区别在 +-0null 的判断上

7.png

8.png

Object.is 认为 +0-0 是不相同的,NaNNaN 是相同的,严格相等与之相反

总结

  • 在比较两个操作数时,双等号(==)将执行类型转换,并且会按照 IEEE 754 标准对 NaN-0 和 +0 进行特殊处理(故 NaN != NaN,且 -0 == +0);

  • 三等号(===)做的比较与双等号相同(包括对 NaN-0 和 +0 的特殊处理)但不进行类型转换;如果类型不同,则返回 false

  • Object.is() 既不进行类型转换,也不对 NaN-0 和 +0 进行特殊处理(这使它和 === 在除了那些特殊数字值之外的情况具有相同的表现)

  • 上述三个操作分别与 JavaScript 四个相等算法中的三个相对应:

  • 🔗IsLooselyEqual==

  • 🔗IsStrictlyEqual===

  • 🔗SameValueObject.is()

| x | y | == | === | Object.is | | :---------------: | :---------------: | :-----: | :-----: | :-------: | | | undefined | undefined | ✅ true | ✅ true | ✅ true | | null | null | ✅ true | ✅ true | ✅ true | | true | true | ✅ true | ✅ true | ✅ true | | 'foo' | 'foo' | ✅true | ✅ true | ✅ true | | 0 | 0 | ✅ true | ✅ true | ✅ true | | +0 | -0 | ✅ true | ✅ true | ❌ false | | +0 | 0 | ✅ true | ✅ true | ✅ true | | -0 | 0 | ✅ true | ✅ true | ❌ false | | 0n | -0n | ✅ true | ✅ true | ✅ true | | 0 | false | ✅ true | ❌ false | ❌ false | | "" | false | ✅ true | ❌ false | ❌ false | | "" | 0 | ✅ true | ❌ false | ❌ false | | '0' | 0 | ✅ true | ❌ false | ❌ false | | '17' | 17 | ✅ true | ❌ false | ❌ false | | [1, 2] | '1,2' | ✅ true | ❌ false | ❌ false | | new String('foo') | 'foo' | ✅ true | ❌ false | ❌ false | | null | undefined | ✅ true | ❌ false | ❌ false | | null | false | ❌ false | ❌ false | ❌ false | | undefined | false | ❌ false | ❌ false | ❌ false | | { foo: 'bar' } | { foo: 'bar' } | ❌ false | ❌ false | ❌ false | | new String('foo') | new String('foo') | ❌ false | ❌ false | ❌ false | | 0 | null | ❌false | ❌ false | ❌ false | | 0 | NaN | ❌false | ❌ false | ❌ false | | 'foo' | NaN | ❌ false | ❌false | ❌ false | | NaN | NaN | ❌ false | ❌ false | ✅ true |

详细信息可以查看🔗相等性方法比较

在项目中,我个人推荐还是使用严格比较或者是Object.is,虽然可能要多写一些转化代码,但是对于变量类型的认知更加清晰,也可以避免一些因类型而引发出的错误