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

前言

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

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

宽松比较 ==

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

js 复制代码
'1' == 1 // true
'1' == true // true
 1 == false // false

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

js 复制代码
1 == [1] // ?
{a:1} == "{a:1}" // ?

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

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

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

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

console.log(+object1); // 42

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

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

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

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

❤️对于对象来说,执行toString可以返回字符串 [object Object]

❤️对于数组来说,toString 返回的是表示 🔗数组元素组成的字符串,相当于调用arr.join 方法

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

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

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

myObj == "2"  // false
// 执行了 valueOf
myObj == 1  // true
js 复制代码
let myObj = {
// valueof 返回引用类型
 valueOf(){
  return []
 },
 // toString 返回字符串2
 toString(){
  return '2'
 }
}

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

myObj == 1  // false

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

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

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

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

例子

  1. 一个操作数是数字,另一个是字符串
js 复制代码
'123' == 123

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

  1. 一个操作数是布尔值
js 复制代码
false == 0
false == '0'

当其中一个操作数是 布尔值 时,true 转换为 1,false 转换为 0

所以 false == 0 相当于 数字0 == 数字0,所以返回 true

当另一个操作数是字符串的时候,布尔值依然会转化

会转化为 数字0 == 字符'0',此时的情况又回到了例子1, 所以也是返回 true

  1. 一个操作数是对象
js 复制代码
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 的判断上

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,虽然可能要多写一些转化代码,但是对于变量类型的认知更加清晰,也可以避免一些因类型而引发出的错误

相关推荐
香蕉可乐荷包蛋27 分钟前
浅入ES5、ES6(ES2015)、ES2023(ES14)版本对比,及使用建议---ES6就够用(个人觉得)
前端·javascript·es6
未来之窗软件服务1 小时前
资源管理器必要性———仙盟创梦IDE
前端·javascript·ide·仙盟创梦ide
liuyang___2 小时前
第一次经历项目上线
前端·typescript
西哥写代码2 小时前
基于cornerstone3D的dicom影像浏览器 第十八章 自定义序列自动播放条
前端·javascript·vue
清风细雨_林木木2 小时前
Vue 中生成源码映射文件,配置 map
前端·javascript·vue.js
FungLeo3 小时前
node 后端和浏览器前端,有关 RSA 非对称加密的完整实践, 前后端匹配的代码演示
前端·非对称加密·rsa 加密·node 后端
不灭锦鲤3 小时前
xss-labs靶场第11-14关基础详解
前端·xss
不是吧这都有重名3 小时前
利用systemd启动部署在服务器上的web应用
运维·服务器·前端
霸王蟹3 小时前
React中巧妙使用异步组件Suspense优化页面性能。
前端·笔记·学习·react.js·前端框架
Maỿbe4 小时前
利用html制作简历网页和求职信息网页
前端·html