WTFJS | 聊一聊JS中的隐式类型转换
有趣的例子
先来看几个有趣的例子
❓ []
等于 ![]
[] == ![]; // -> true
❓ true
不等于[]
, 也不等于 ![]
true == []; // -> false
true == ![]; // -> false ???
❓ null & false
!!null; // -> false
null == false; // -> false ???
❓ []
& {}
console.log([] + {}); ???
上述问题截取自 wtfjs ( 即What the f*ck JavaScript? ) ,以下是其对于JavaScript的部分描述。
JavaScript is quite a funny language with tricky parts. Some of them can quickly turn our everyday job into hell, and some of them can make us laugh out loud.
尤其是对于js中的类型转换相关问题,可以说是非常贴切了。该仓库中列出的大部分
tricky parts
均是与类型转换相关的,更具体地说应该是隐式类型转换,这也是我为什么想写这篇文章的原因。网上有很多关于类型转换的文章,文末也列出了一些参考文章。
如果你能熟练的得出上述问题的答案,说明你对于JavaScript中的隐式类型转换已经挺熟悉了,如果对此不是很熟悉,那么也没关系,接下来我们一起来梳理一下JavaScript中的隐式类型转换。
前置知识
什么是ToPrimitive?
规范中的解释在这里:9.1 ToPrimitive
以下文字出自冴羽大佬的JavaScript深入之头疼的类型转换(下)
函数语法表示如下:
ToPrimitive(input[, PreferredType])
第一个参数是 input,表示要处理的输入值。
第二个参数是 PreferredType,非必填,表示希望转换成的类型,有两个值可以选,Number 或者 String。
当不传入 PreferredType 时,如果 input 是日期类型,相当于传入 String,否则,都相当于传入 Number。
如果传入的 input 是 Undefined、Null、Boolean、Number、String 类型,直接返回该值。
如果是 ToPrimitive(obj, Number),处理步骤如下:
- 如果 obj 为 基本类型,直接返回
- 否则,调用 valueOf 方法,如果返回一个原始值,则 JavaScript 将其返回。
- 否则,调用 toString 方法,如果返回一个原始值,则 JavaScript 将其返回。
- 否则,JavaScript 抛出一个类型错误异常。
规范怎么说?
以下是涉及隐式类型转换的一些常见类型
1. 一元操作符 +
ES5规范1.4.6 简单的说就是会调用toNumber方法去处理,具体结果如下
类型 | 结果 |
---|---|
Undefined | NaN |
Null | +0 |
Boolean | true >>> 1; false >>> 0 |
Number | 直接返回 |
String | 具体见下方具体实例 |
Object | 先调用ToPrimitive得到primValue,然后返回ToNumber(primValue) |
string 转数字
console.log(Number()) // +0 console.log(Number(undefined)) // NaN console.log(Number(null)) // +0 console.log(Number(false)) // +0 console.log(Number(true)) // 1 console.log(Number("123")) // 123 console.log(Number("-123")) // -123 console.log(Number("1.2")) // 1.2 console.log(Number("0x11")) // 17 console.log(Number("")) // 0 console.log(Number(" ")) // 0 console.log(Number("123 123")) // NaN console.log(Number("foo")) // NaN console.log(Number("100a")) // NaN console.log(Number("1.2.2")) // NaN console.log(Number(".2")) // 0.2
可以看到toNumber(string)
有如下特点:
- 尝试将传输值转换为整数或浮点数
- 忽略前导0
- 出现不是数字的字符,返回NaN (遇到的第一个
.
会被当做小数点处理)
2. 二元操作符 +
以 x + y 为例
prim_x = ToPrimitive(x)
prim_y= ToPrimitive(y)
prim_x
或者prim_y
其中一个为字符串,返回ToString(prim_x)
和ToString(prim_y)
的拼接结果- 返回
ToNumber(prim_x)
和ToNumber(prim_y)
的运算结果
console.log(null + 1);
console.log([] + []); //相当于两个空字符串拼接
console.log([] + {}); // 相当于 "" + "[object Object]"
console.log({} + []); // 相当于 "[object Object]" + ""
PS: {} + [] 直接在chromes控制台运行结果是0,因为第一个{}被当作一个空代码块,所以这段代码在这种情况下相当于
+[]
, 也就是toNumber([])
,故返回 0。如果
({} + [])
这样直接运行的话就会和console的方式得到相同的结果。那这样的话在控制台中直接运行
{} +{}
,是不是得0呢?并不会。会得到
'[object Object][object Object]'
一些在**WTFJS
**中出现的一些例子:
// baNaNa
"b" + "a" + +"a" + "a"; // -> 'baNaNa'
"foo" + +"bar"; // -> 'fooNaN'
// Adding arrays
[1, 2, 3] + [4, 5, 6]; // -> '1,2,34,5,6'
// Math with true and false
true + true; // -> 2
(true + true) * (true + true) - true; // -> 3
// Funny math
3 - 1 // -> 2
3 + 1 // -> 4
'3' - 1 // -> 2
'3' + 1 // -> '31'
'' + '' // -> ''
[] + [] // -> ''
{} + [] // -> 0
[] + {} // -> '[object Object]'
{} + {} // -> '[object Object][object Object]'
'222' - -'111' // -> 333
// 这种形式,用猜的方式也能直到需要toNumber
[4] * [4] // -> 16
[] * [] // -> 0
[4, 4] * [4, 4] // NaN
3. ==
11.9.3 The Abstract Equality Comparison Algorithm
以下截取了规范中关于 The Abstract Equality Comparison Algorithm
的相关描述,嫌太长可以跳过,看总结部分。
- If Type(x) is the same as Type(y), then
- If Type(x) is Undefined, return true.
- If Type(x) is Null, return true.
- If Type(x) is Number, then
- If x is NaN, return false.
- If y is NaN, return false.
- If x is the same Number value as y, return true.
- If x is +0 and y is −0**, return **true.
- If x is −0 and y is +0, return true.
- Return false.
- If Type(x) is String, then return true if x and y are exactly the same sequence of characters (same length and same characters in corresponding positions). Otherwise, return false.
- If Type(x) is Boolean, return true if x and y are both true or both false. Otherwise, return false.
- Return true if x and y refer to the same object. Otherwise, return false.
- If x is null and y is undefined, return true.
- If x is undefined and y is null, return true.
- If Type(x) is Number and Type(y) is String,
return the result of the comparison x == ToNumber(y).- If Type(x) is String and Type(y) is Number,
return the result of the comparison ToNumber(x) == y.- If Type(x) is Boolean, return the result of the comparison ToNumber(x) == y.
- If Type(y) is Boolean, return the result of the comparison x == ToNumber(y).
- If Type(x) is either String or Number and Type(y) is Object,
return the result of the comparison x == ToPrimitive(y).- If Type(x) is Object and Type(y) is either String or Number,
return the result of the comparison ToPrimitive(x) == y.- Return false.
总结( x == y)
-
如果
x
与y
类型相同的话,分为以下几种情况:- 都是
undefined
或者都是null
,返回true
- 都是Number
- 其中一方是
NaN
,返回false
- 数值相同,返回
true
- +0 和 -0 比较, 返回
true
- 其中一方是
- 都是
-
如果x与y类型不同的话
-
null 和 undefined ,对应2、3
console.log(null == undefined); //true
-
字符串与数字比较, 对应4、5
都转换为数字进行比较
-
x或者y其中一方为布尔量, 对应6、7
console.log(true == '2')
-
x或者y一方为对象,另一方为字符串或者数字
若x为对象,ToPrimitive(x) == y,
若y为对象,x == ToPrimitive(y)
一些在*WTFJS`中出现的一些例子:
-
// baNaNa
"b" + "a" + +"a" + "a"; // -> 'baNaNa'
"foo" + +"bar"; // -> 'fooNaN'
// Adding arrays
[1, 2, 3] + [4, 5, 6]; // -> '1,2,34,5,6'
// Math with true and false
true + true; // -> 2
(true + true) * (true + true) - true; // -> 3
// Funny math
3 - 1 // -> 2
3 + 1 // -> 4
'3' - 1 // -> 2
'3' + 1 // -> '31'
'' + '' // -> ''
[] + [] // -> ''
{} + [] // -> 0
[] + {} // -> '[object Object]'
{} + {} // -> '[object Object][object Object]'
'222' - -'111' // -> 333
// 这种形式,用猜的方式也能直到需要toNumber
[4] * [4] // -> 16
[] * [] // -> 0
[4, 4] * [4, 4] // NaN
其他
这一部分列举了其他WTFJS
中一些其他的案例,有兴趣的可以看看
- It’s a fail
// It's a fail
(![] + [])[+[]] +
(![] + [])[+!+[]] +
([![]] + [][[]])[+!+[] + [+[]]] +
(![] + [])[!+[] + !+[]];
// -> 'fail'
第一眼看到这一长串式子,心里:💢 MD这都啥跟啥呀!!!💢 , 但是如果认真看过上面提到的知识,耐心一点是能够得到这个fail
结果的。
具体过程这里就不在赘述,It’s a fail 中有详细的过程解释。
- null 与 0
null == 0; // false //1
null > 0; // false //2
null >= 0; // true //3
首先看第一个,不要先入为主的将null 转换为0 再进行比较,仔细回想一下,会发现之前规范中并没有这样的规则说要在这里把null转换为0, 刚刚与预想的情形根本不会出现。
如果在 == 两边出现了一个null, 那么只有另一边也是null 或者是 undefined的情况下,返回值财位 true, 对应**11.9.3 The Abstract Equality Comparison Algorithm**中2、3.
接下来将2 和 3 放在一起看,null 不大于0 , 同时null 又大于等于0 ❓❓❓
对于2, 判断的流程应该如下:
null > 0
+null = +0
0 > 0
false
而对于null >= 0
, 可能会猜想出现这种情况:null > 0 || null == 0
, 但这样会返回false
,所以很明显不是。原因在于 >=
以一种不同的方式运作,详见11.8.4 The Greater-than-or-equal Operator ( >= ) 。简单地说就是 当 null >= 0
进行比较时,依据是 null < 0
的结果,如果 null < 0
为true, 那么返回 false
; 反之返回true
除开上述提到的一些部分,WTFJS中还有一些其他实例,有兴趣可以去查看,另外其中的中文翻译文档貌似有些错误,直接看英文版就好。
参考: