很差意思作了一回标题党,JavaScript 中历来就没有什么奇怪的事件,我只是想梳理一下 javascript 中让人疑惑的表达式以及背后的原理。javascript
好比请说出如下这些表达式的结果:html
1 + '1'
1 - '1'
'2' + '2' - '2'
[] + []
{} + {}
[] + {}
{} + []
[] + {} === {} + []
{} + [] === [] + {}
[+false] + [+false] + [+false]
[+false] + [+false] + [+false] - [+false]
'1' == true
parseInt('infinity') == 0 / 0
1 < 2 < 3
3 > 2 > 1
isNaN(false)
isNaN(null)
[[][[]]+[]][+[]][++[+[]][+[]]]
若是想知道正确答案的话把表达式粘贴到浏览器的控制台执行便可前端
接下来的内容就是讲解这些表达式的结果是在什么样的原理下得出的java
解决以上的问题的关键在于要搞明白三点:chrome
+
操做符+
在 JavaScript 中有三个做用:数组
var result = 'Hello' + 'World'
var result = 1 + 2
+variable
在表达式中+
是操做符(operator),操做符操做的对象(上面例子中的Hello
、 World
、 1
、 2
)名为操做数(operand)浏览器
一元+
操做符的运算规则是:ToNumber(ToPrimitive(operand))
,也就是把任意类型都转化为数字类型。ide
当操做数的数据类型不一致时,会根据如下规则进行转化:code
object
),则须要将它转化为基础类型(primitive
),即字符串、数字或者布尔
Date
类型,那么调用toString()
方法valueOf()
方法valueof()
方法不存在或者并无返回一个基础类型,那么调用toString()
join(',')
方法{}
转化的结果是 [object Object]
因此根据以上规则,咱们就能解释:htm
1 + '1'
的结果是 '11'
,由于其中一个是操做数是字符串,因此另外一个操做数也被转化为字符串,而且执行字符串链接操做[] + []
的结果是 ''
空字符串,由于数组是对象类型,转化为基础类型的结果是空字符串,拼接以后仍然是空字符串[] + {}
的结果是 [object Object]
,由于操做数有对象类型的关系,两个操做数都须要转化为基础类型,[]
转化为基础类型的结果是''
,{}
转化为基础类型的结果是[object Object]
,最后字符串拼接的结果仍然是[object Object]
接下来咱们说一说值得注意的状况
{} + []
的结果是0
。由于在这个表达式中,开头{}
并非空对象的字面量,而是被看成空的代码块。事实上这个表达式的值就是+[]
的结果,即Number([].join(','))
,即为0
更奇怪的是{} + {}
这个表达式,在不一样的浏览器中执行会获得不一样的结果。 按照上面的例子,咱们能够同理推出这个表达式的值其实是+{}
的值,即最后的结果是Number([object Object])
,即NaN
。在 IE 11 中的执行结果倒是是如此,可是若是在 Chrome 中执行,你获得的结果是 [object Object][object Object]
。
根据 Stackoverflow上的回答 这是由于 Chrome devtools 在执行代码的时候隐式的给表达式添加了括号()
,实际上执行的代码是({} + {})
。若是你在 IE 11 中执行({} + {})
,就会获得[object Object][object Object]
的结果
虽然上面咱们已经明确了 [] + {}
的结果是 [object Object]
,而 {} + []
的结果是0
,可是若是把他们进行比较的话:[] + {} === {} + []
结果会是true
。由于右侧的{}
跟随在===
以后的关系,再也不被认为是空的代码块,而是字面量的空对象,因此两侧的结果都是[object Object]
{} + [] === [] + {}
一样是一个有歧义的结果,理论上来讲表达式的返回值是false
,在 IE 11 中确实如此,可是在 Chrome 的 devtools 中返回 true
,缘由仍然是表达式被放在()
中执行
[+false] + [+false] + [+false]
的结果也可想而知了,+false
的结果是false
转化为数字0
,以后[0]
又被转化为基础类型字符串'0'
,因此表达式最后的结果是'000'
-
操做符虽然-
操做符和+
操做符看看上去性质相同,但-
操做符只有一个功能,就是数值上的相减。它会尝试把非数值类型的操做数转化为数值类型,若是转化的结果是NaN
, 那么表达式的结果可想而知也就是NaN
,若是所有都转化成功,则执行减法操做,因此
1 - '1'
实际上执行的是 1 - 1
,结果为 0
'2' + '2' - '2'
表达式首先要遵循从左至右的执行顺序,'2' + '2'
的执行的是字符串拼接,结果是'22'
,在接下来的'22' - '2'
计算中两个操做数都成功的转化为了数字,结果是数字相减的结果20
[+false] + [+false] + [+false] - [+false]
表达式实际上执行的是'000' - '0'
,最后的结果也就是数字0
==
操做符在 JavaScript 中===
称为恒等操做符(The identity operator),==
称为相等操做符(The equality operator)。由于篇幅关系在这里咱们简单的针对题目聊聊后者
若是==
操做符的操做数的数据类型不一样:
null
,而且另一个操做数是undefined
,他们是相等的true
转化为1,false
转化为0在进行比较'1' == true
时,首先将true
转化为数字1
,此时表达式中同时存在数值和字符串类型,再把字符串'1'
转化为数字1
,最终1 == 1
固然成立parseInt('infinity') == 0 / 0
其实是在判断NaN == NaN
,这样的比较是一个特例,不管是在==
比较仍是===
比较中,NaN
不会与任何东西相等;或者说只要有任意操做数是NaN
,那么表达式就会返回false
更全面==
和===
的比较规则请参考: The legend of JavaScript equality operator
比较运算符>
和<
也遵循类似的规则: 1. 优先将字符串转化为数字进行比较;2. 将布尔类型转化为数字再进行比较,
1 < 2 < 3
中,首先执行1 < 2
,结果为true
,可是在比较true < 3
的过程当中,须要把true
转化为数值类型1
,最终比较1 < 3
,返回值为 true
3 > 2 > 1
中,最终比较的实际上是true > 1
,也便是1 > 1
固然返回的是false
isNaN
"NaN"是"Not a Number"的缩写,咱们觉得isNaN
可以直接用来判断值是不是数字类型,但实际上并不能够。由于isNaN
首先会强制将参数转化为数值类型,再进行判断。 这也就不难解释为何isNaN(false)
和isNaN(null)
返回都是true
,由于false
和null
都能被成功转化为数字0
, 因此对于isNaN
来讲,它们是数字
最后咱们以表达式[[][[]]+[]][+[]][++[+[]][+[]]]
做为文章的结尾
在这个表达式中出现了三种操做符,分别是
[]
+
+
++
根据操做符的优先次序表,咱们能肯定操做符的优先级依次是: []
> 一元操做符+
> ++
> +
因此根据优先级咱们首先能够计算出表达式的+[]
部分,而且将表达式的这一部分用计算结果替换掉: [[][[]]+[]][0][++[0][0]]
接下来咱们把表达式拆分为三部分看待: [ [][[]]+[] ] [0] [ ++[0][0] ]
。若是仍是不清晰的话,三部分从左到右分别是:
[ [][[]]+[] ]
[0]
[ ++[0][0] ]
咱们先看第一部分中+
前面的 [][[]]
操做数,第一个[]
是空数组,而紧跟着的[[]]
是属性访问器(成员操做符),属性访问器内的[]
会被强制转化为字符串类型,最终的结果便是空字符串''
,因此第一个操做数的最终结果实际上是[]['']
,便是undefined
,而又由于+
操做符的规则,最终[][[]]+[]
表达式的结果是字符串'undefined'
,那么现阶段表达式的结果是['undefined'][0][++[0][0]]
,即'undefined'[++[0][0]]
接下来咱们解决第三部分: [++[0][0]]
,我已经知道成员操做符[]
的优先级要高于自增操做符++
, 因此关于表达式++[0][0]
,咱们须要首先计算[0][0]
,结果是0
,以后计算++0
的结果便是1
因此最终表达式转化为了'undefined'[1]
,最终的结果便是'n'
本文也同时发布在个人知乎专栏前端技术漫游指南 上,欢迎你们关注