纵横研究院前端基础技术专题社区

ToPrimitive == === > <

2019-10-11  本文已影响0人  在幽幽暗暗反反复复中追问

参考《你不知道的JavaScript(中卷)》第四章

理解 ToPrimitive 操作 就能理解了JS 中的有点迷的 == 操作了

// 以下是有点奇怪的 == 
"0" == false; // true
false == 0; // true
false == ""; // true
false == []; // true
"" == 0; // true
"" == []; // true
0 == []; // true

ToPrimitive 操作

抽象操作 ToPrimitive(参见 ES5 规范 9.1 节)会首先(通过内部操作 DefaultValue,参见 ES5 规范 8.12.8 节)检查该值是否有 valueOf() 方法。
如果有并且返回基本类型值,就使用该值进行强制类型转换。如果没有就使用 toString() 的返回值(如果存在)来进行强制类型转换。
如果 valueOf() 和 toString() 均不返回基本类型值,会产生 TypeError 错误。

什么是 ToNumber

true 转换为 1, false 转换为 0。 undefined 转换为 NaNnull 转换为 0。ToNumber 对字符串的处理基本遵循数字常量的相关规则 / 语法。处理失败时返回 NaN(处理数字常量失败时会产生语法错误)。不同之处是 ToNumber 对以 0 开头的十六进制数并不按十六进制处理(而是按十进制)

== 和 ===

== 允许在相等比较中进行强制类型转换,而 === 不允许。
实际上 ===== 都会检查操作数的类型。区别在于操作数类型不同时它们的处理方式不同。

特别情况

NaN == NaN  // false
+0 == -0 // true

ES5 规范 11.9.3.1 的最后定义了对象(包括函数和数组)的宽松相等(==):两个对象指向同一个值时即视为相等,不发生强制类型转换。
在比较两个对象的时候, ===== 的工作原理是一样的

== 比较不同类型时发生了什么

显然会进行类型转换
基本数据类型都转为数字
对象类型进行 ToPrimitive 操作

转换的优先级是 布尔 > 字符串 > 对象

最终他们都会转为 数字 类型(因为基本数据类型都会转为数字类型,对象的转换优先级最低,轮到对象进行转换的时候,另外一个需要转换的操作数早就转为数字了,如果 ToPrimitive 操作返回的结果非数字,那么要进行 == 操作的两个操作数的类型依然不同, ToPrimitive 操作返回的结果还需要转为数字)

举个例子,布尔值和字符串比较:

false == "abc"  // false
// 转换的优先级是 布尔 > 字符串,所以 false 先转为数字得到 0
// 现在相当于比较  0 == "abc",操作数的类型还是不同,继续类型转换
// "abc" 转为数字,Number("abc")  得到 NaN
// 0 == NaN 结果 false 

布尔值和对象比较:

false == []  // true
// 转换的优先级是 布尔 > 对象,所以 false 先转为数字得到 0
// 现在相当于比较  0 == [],操作数的类型还是不同,继续类型转换
// [] 是对象,进行 ToPrimitive 操作,得到空字符,
// 现在相当于比较 0 == "",操作数的类型还是不同,继续类型转换
// 空字符是基本数据类型,转为数字,Number("")  得到 0
// 0 == 0 结果 true 

那么 null 和 undefined 呢?这里引出另外一个问题:包装类型(看最后)
他们不会进行转换
null 只会 == undefined 或者自身,undefined 同样

null == null        // true
null == undefined   // true
undefined == undefined  // true

对 [] 和 {} 呢?
以下代码不是说明 [] == {} 发生了什么,因为两个对象进行宽松或者严格相等时,不进行类型转换,两个对象指向同一个值时即视为相等

// 对象和基本数据类型进行宽松比较时,对象发生了什么?
// 当对象是 []
[].valueOf()  // 还是一个对象
[].toString()  // ''  一个空字符串
Number("")  // 0

// 当对象是 [2,3]
[2,3].valueOf()  // 还是一个对象
[2,3].toString()  // "2,3"
Number("2,3")  // NaN

// 当对象是 [null]
[null].valueOf()  // 还是一个对象
[null].toString()  // ""
Number([null])  // 0
/*
 也许你认为 [null].toString() 返回的不是 ""
 但是如果不这样处理的话又能怎样呢?
 有人也许会觉得既然 String(null) 返回 "null"
 所以 String([null]) 也应该返回 "null"。
 确实有道理,实际上这是 String([..]) 规则的问题。
 又或者根本就不应该将数组转换为字符串?
 但这样一来又会导致很多其他问题
*/

// 当对象是 {}
({}).valueOf()  // 还是一个对象
({}).toString() // "[object Object]"  
Number( "[object Object]")  // NaN

>, <, <=

这属于抽象关系比较:
比较双方首先调用 ToPrimitive,如果结果出现非字符串,就根据 ToNumber 规则将双方强制类型转换为数字来进行比较。

实际上 JavaScript 中 <= 是 “不大于” 的意思(a <= b 被处理为 b < a,然后将结果反转。)即 a <= b ,处理为 !(b < a)。

相等比较有严格相等,关系比较却没有“严格关系比较”(strict relational comparison)。也就是说如果要避免 a < b 中发生隐式强制类型转换,我们只能确保 a 和 b 为相同的类型,除此之外别无他法。

包装类型

基本数据类型:number、string、boolean 都有包装类型,这是为了让这些基本数据类型可以方便地调用一些常用的方法,比如 toString,valueOf 等等

但是 null 和 undefined 没有对应的包装类型,所以 null 和 undefined 不能够被封装(boxed)
Object(null) 和 Object() 均返回一个常规对象。

“拆封”,即“打开”封装对象(如 new String("abc")),返回其中的基本数据类型值("abc")。

以上说的和 == 有什么关系?
因为 == 中的 ToPromitive 强制类型转换也会发生拆封,这大概就是很多人错误地认为 == 不进行类型判断的原因(我猜的)

var a = "abc";
var b = Object( a ); // 和new String( a )一样
a === b; // false
a == b; // true

Object(null) 和 Object() 均返回一个常规对象,没法拆封。

var a = null;
var b = Object( a ); // 和Object()一样
a == b; // false
var c = undefined;
var d = Object( c ); // 和Object()一样
c == d; // false
var e = NaN;
var f = Object( e ); // 和new Number( e )一样
e == f; // false
上一篇下一篇

猜你喜欢

热点阅读