前言

类型转换在各个语言中都存在,而在 JavaScript 中由于缺乏对其的了解而不慎在使用中经常造成bug被人诟病。为了避免某些场景下的意外,甚至推崇直接使用 Strict Equality( === )来代替 ==(最新的eslint规则默认就是使用===)。这确实能避免很多bug,但更是一种对语言不理解的逃避(个人观点)。

来看几道经典的题目:

1
2
3
4
[] == [] // false
[] == ![] // true
{} == !{} // false
{} == {} // false
是不是很奇怪?本文将从书中看到的知识与规范相结合,来详细说明一下JavaScript在类型转换时候发生的故事。

JS的数据类型

首先,回想一下JS的类型都有什么。
原始值(primitives): undefined, null, booleans, numbers,strings, symbol(es6)
对象值(objects): Object

ToPrimitive

在发生转换的时候,js其实都是会将操作对象转化为原始的对象,这也是最为诟病的地方,因为js很难直接抛出错误,她会用一套自己的方法去理解我们的错误,并做相应的调整,哪怕这些错误我们是无意识的。所以我们要知道她的转换方式,才能做到知己知彼,对代码的控制更为精准。

签名:ToPrimitive(input, PreferredType?) //PreferredType: Number 或者 String
流程如下:

input为原始值,直接返回;
不是原始值,调用该对象的valueOf()方法,如果结果是原始值,返回原始值;
调用valueOf()不是原始值,调用此对象的toString()方法,如果结果为原始值,返回原始值;
如果返回的不是原始值,抛出异常TypeError。
其中PreferredType控制线调取valueOf()还是toString()。

ps: Date类型按照String去调用。

总结:==操作符

比较运算 x==y,其中x和y是值,产生true或者false。这样的比较如下方式进行:

一、若Type(x)与Type(y)相同,则:
   a.若Type(x)为Undefined,返回true。
   b.若Type(x)为Null,返回true。
   c.若Type(x)为Number,则:
    1.若x为NaN,返回false。
    2.若y为NaN,返回false。
    3.若x与y为相等数值,返回true。
    4.若x为+0且y为-0,返回true。
    5.若x为-0且y为+0,返回true。
    6.返回false。
   d.若Type(x)为String,则当x和y完全相等的字符序列(长度相等且相同字符在相同位置)时返回true。否则,返回false。
   e.当Type(x)为Boolean,当x和y同为true或者同为false时返回true。否则,返回false。
   f.当x和y为引用同一对象时返回true。否则,返回false。
二、若x为null且y为undefined,返回true。
三、若x为undefined且y为null,返回true。
四、若Type(x)为number且Type(y)为String,返回comparison x==ToNumber(y)的结果。
五、若Type(x)为String且Type(y)为Number,返回comparison ToNumber(x) == y的结果。
六、若Type(x)为Boolean,返回比较ToNumber(x) == y的结果。
七、若Type(x)为Boolean,返回比较x == ToNumber(y)的结果。
八、若Type(x)为String或Number,且Type(y)为Object,返回比较x == ToPrimitive(y)的结果。
九、若Type(x)为Object且Type(y)为String或Number,返回比较ToPrimitive(x) == y的结果。
十、返回false。

上图中的 toPrimitive 就是对象转基本类型。
这里来解析一道题目 [] == ![] // -> true ,下面是这个表达式为何为 true 的步骤
1
2
3
4
5
6
7
8
9
10
11
// [] 转成 true,然后取反变成 false
[] == false
// 根据第 8 条得出
[] == ToNumber(false)
[] == 0
// 根据第 10 条得出
ToPrimitive([]) == 0
// [].toString() -> ''
'' == 0
// 根据第 6 条得出
0 == 0 // -> true

备注

#####ToString
按照以下规则转化被传递的参数

ArgumentType Result
Undefined “undefined”
Null “null”
Boolean true -> “true”false – > “false”
Number 1:’NaN -> “NaN”。 2:+0 -0 -> “0”。 3:-1 -> “-1。 4:”infinity -> “Infinity”
String 不转换 直接返回
Object 1:调用ToPrimitive抽象操作, hint 为 String 将返回值作为 value。2:返回ToString(value)。
1
2
3
String(undefined) // "undefined"
String(null) // "null"
String(true) // "true"
ToNumber

按照以下规则转换被传递参数

Argument Type Result
Undefined NaN
Null +0
Boolean 1:true -> 1。 2:false -> +0。
Number 直接返回
String 如果不是一个字符串型数字,则返回NaN(具体规则见规范9.3.1)
Object 1:调用ToPrimitive抽象操作, hint 为 Number 将返回值作为 value。 2:返回ToNumber(value)。
ToBoolean

按照以下规则转换被传递参数

Argument Type Result
Undefined false
Null false
Boolean 直接返回
Number 1:+0 -0 NaN -> false。 2:其他为true。
String 1:空字符串(length为0) -> false。 2:其他为true。
Object true
ToPrimitive

顾名思义,该抽象操作定义了该如何将值转为基础类型(非对象),接受2个参数,第一个必填的要转换的值,第二个为可选的hint,暗示被转换的类型。

按照以下规则转换被传递参数

Argument Type Result
Undefined 直接返回
Null 直接返回
Boolean 直接返回
Number 直接返回
String 直接返回
Object 返回一个对象的默认值。一个对象的默认值是通过调用该对象的内部方法[[DefaultValue]]来获取的,同时传递可选参数hint。
[[DefaultValue]] (hint)

当传递的hint为 String 时候,

  • 如果该对象的toString方法可用则调用toString
      如果toString返回了一个原始值(除了object的基础类型)val,则返回val
        如果该对象的valueOf方法可用则调用valueOf方法
      如果valueOf返回了一个原始值(除了object的基础类型)val,则返回val
        抛出TypeError的异常
      当传递的hint为 Number 时候,
  • 如果该对象的valueOf方法可用则调用valueOf方法
      如果valueOf返回了一个原始值(除了object的基础类型)val,则返回val
        如果该对象的toString方法可用则调用toString
      如果toString返回了一个原始值(除了object的基础类型)val,则返回val
        抛出TypeError的异常
  • hint的默认值为Number,除了Date object
    举个栗子
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    var a = {}
    a.toString = function () {return 1}
    a.valueOf = function () {return 2}
    String(a) // "1"
    Number(a) // 2
    a + '' // "2" ???????
    +a // 2
    a.toString = null
    String(a) // "2"
    a.valueOf = null
    String(a) // Uncaught TypeError: balabala
    似乎我们发现了一个很不合规范的返回值,为什么 a + ‘’不应该返回”1″吗

问题的答案其实很简单 + 操作符会对两遍的值进行 toPrimitive 操作。由于没有传递 hint 参数,那么就会先调用a.valueOf 得到2后因为+右边是字符串,所以再对2进行ToString抽象操作后与””的字符串拼接。