javascript中的===、==、隐式转换、布尔判定

581 阅读6分钟

前言

javascript 中对于不同参数比较经常会碰到 ==、===,而使用过程中一般很多问题都会出现在 ==,因此很多人一般都是无脑使用 === 符号,这个 == 实际应用中如果不是很了解,一般确实是不推荐使用的(但也不代表其没有使用空间哈),了解后,可以根据实际情况使用

并且还介绍了隐式转换和波尔判定,还有其他的一些细节,例如: ===Object.is 的区别等

=== 操作符

=== 是比较严谨的比较符号,其他语言中不常见,不仅仅比较值,同时比较类型,仅仅当类型 + 值相等时,才会返回 true,而对于对象都是指针的比较,同一个对象返回 true,不同返回 false

console.log(1 === '1'); //false

console.log(1 === 1); //true

//因为不是一个对象了
console.log([] === []) //false

const list = []
const list2 = list
//两个指针指向同一个对象,所以为true
console.log(list === list2) //true
//实际上对象之间默认都是使用指针地址比较,因此对象之间 == 即可
console.log(list == list2) //true

ps:其类似于 Object.is 判断,只不过 Object.is 判断中 +0、-0 不相同=== 判定则是相同

== 操作符:

== 是比较宽松的比较符号,在其他很多语言中基本中和 ===差不多,甚至类型不同会报错,然而在 js 中由于本身宽松的类型,就存在非常多的可能,而 ==宽松的比较方式能够带来很多便利,同时带来很多问题

== 的比较规则如下:

  • 如果操作数具有相同的类型,则按如下方式进行比较:
    • 对象(Object):仅当两个操作数引用同一个对象时返回 true(null实际上也是特殊Object类型别忘了,可以理解为null是一个起名为null的Object全局对象);
    • 字符串(String):只有当两个操作数具有相同的字符且顺序相同时才返回 true ;
    • 数值(Number):如果两个操作数的值相同,则返回 true 。+0 和 -0 被视为相同的值。如果任一操作数为 NaN ,则返回 false ;因此 NaN 永远不等于 NaN ;
    • 布尔值(Boolean):仅当操作数都是 true 或 false 时,才返回 true ;
    • 大整形(BigInt):仅当两个操作数具有相同的值时,才返回 true ;
    • 符号(Symbol):仅当两个操作数引用相同的符号时,才返回 true ;
    • 如果操作数之一为 null 或 undefined ,则另一个操作数必须为 null 或 undefined 才返回 true 。否则返回 false ;
  • 如果操作数之一是对象,而另一个是原始值, 则将对象则会进行隐形转换转换为原始值(可以参考下一小节) ,在这一步骤中,两个操作数都被转换为原始值(String、Number、Boolean、Symbol 和 BigInt 之一)
    • 例如:[1,2,3] 与字符串对比时,会转化为 "1,2,3",空数组则对应空字符串
  • 如果操作数中有一个是 Symbol,但另一个不是,则返回 false
  • 如果操作数之一是 Boolean,而另一个不是, 则将 Boolean 转换为 Number,true 转换为 1,false 转换为 0,然后再次对两个操作数进行宽松比较
  • Number == String: 将 String 转换为 Number 。转换失败会得到 NaN ,这将确保意外行为出现,毕竟 NaN 表示非数字,甚至自身都不认为是相同的
  • Number == BigInt:按照其数值进行比较。如果 Number 是 ±Infinity 或 NaN,一个无穷大一个非数字,没办法比较,因此返回 false
  • String == BigInt: 使用与 BigInt () 构造函数相同的算法将字符串转换为 BigInt。如果转换失败,则返回 false
  • null 和 undefined,虽然类型不一致,判断时他们两个是按照相同处理,并且他们与其他基础类型都不会相同,例如:""、0、false

举几个常见的案例

console.log(true == 1); //true
console.log(1 == '1'); //true
console.log(true == '1'); //true

//数字会转化为字符串相当于去掉中括号
console.log([1,2,3] == '1,2,3');  //true
//空数组会转化为空串
console.log([] == ''); //true

//null 和 undefined 在这里是相等的,实际上 null 是一个空对象哈
console.log(null == undefined);  //true

//null、undefined 是不会等于任何基础类型,包括 ''、false、0
console.log(null == '');  //false
console.log(undefined == '')  //false

隐式转换

如果操作数之一是对象,而另一个是原始值, 则将对象则会进行隐形转换转换为原始值,除了 == 判断,+ 之类的判断也是同理

一个对象的隐式转换要经过一系列方法查找,分别是 [Symbol.toPrimitive]valueOftoString,他们需要返回原始值(数字、字符串、boolean等)

const obj = {
    [Symbol.toPrimitive]: function () {
        return 'toPrimitive'
    },
    valueOf: function () {
        return "valueOf";
    },
    toString: function () {
       return "toString";
    },
};
分别取消注释,则会返回 toPrimitive1、valueOf1、toString1
console.log(obj + 1);

//如果是数字,则按照数字使用
const obj = {
    [Symbol.toPrimitive]: function () {
        return 2
    },
};
//返回 2 这下应该明白了
console.log(obj + 1);

如果返回的不是原始值怎么办

  1. [Symbol.toPrimitive]如果返回不是原始值,则直接报错
  2. valueOf 如果返回不是原始值,则会调用后续的 toString,参考 3;没实现则参考 4
  3. toString如果返回不是原始值,则会直接报错了,如果没实现,则参考 4
  4. 对象如果没有实现 toString,则会返回 [object Object],原始值则自动转换

ps:一个对象什么都没实现,对象则会返回 [object Object] 字符串,原始类型返回字符串,会不会有些版本报错出现其他的就不知道了,尽量避免此类情况出现

布尔判定

除了上面的隐式转换,还有布尔判定,并且布尔判定在原始类型转换之前

//这个返回什么呢,如果默认是对象就会转化原始类型,那么结果就是错的
console.log([] + ![])//返回 'false'

//![],是一个布尔值判定,直接返回 'false',不走隐式转换
//因此前面的[]和原始类型 false进行 + 操作,开始隐式转化为 '',则就是 '' + false = 'false'

相信这下就明白了布尔判定,更了解 布尔判定在原始类型转换之前,因此碰到布尔判断不要再盲目走原始类型转换

常见的布尔判定操作,如下所示

// if (obj) {}
// const res = obj ? ... : ...
// while (obj) { }
// !obj
// obj && ...

console.log('' + false)

Object.is

前面都提到了两个比较符号,这里也顺便提一下比较两个比较常见的函数,Object.is

Object.is===类似,都是严格比较,但是Object.is与其不同的是 NaN、+0、-0之间的比较不一样

Object.is(Nan, Nan) // true
Nan === Nan //false

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

ps:个人猜测造成上面上有些不同的原因,可能就是 === 一般用于两个内容或者值进行比较,而Object.is更倾向于对比两个对象,实际上对比值生成 NaN 和 NaN 的两个内容不应该相同,但是他们同属于一个对象,某些场景是要统一的,此外 +0、-0也是一样,归咎到底是两个不同对象, Object.is返回为false也可以理解,作为更倾向于对比值的 === 自然就是相等的了,当然这只是猜测,实际上他们在特殊的场景自然会理解该怎么使用的😂

最后

本章主要讲了 ===== 严格比较和宽松比较,还扩展了对象操作中的隐式转换布尔判定,基本上能解决碰到的此类问题了