JavaScript 奇怪事件簿

很差意思作了一回标题党,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

  1. 操做符的使用方法和优先级
  2. 操做数在操做符的上下文中数据类型转化规则
  3. 语法中的特例

+ 操做符

+在 JavaScript 中有三个做用:数组

  1. 链接字符串:var result = 'Hello' + 'World'
  2. 计算数字之和:var result = 1 + 2
  3. 做为一元操做符:+variable

在表达式中+是操做符(operator),操做符操做的对象(上面例子中的HelloWorld12)名为操做数(operand)浏览器

一元+操做符的运算规则是:ToNumber(ToPrimitive(operand)),也就是把任意类型都转化为数字类型。ide

当操做数的数据类型不一致时,会根据如下规则进行转化:code

  • 若是至少一个操做数是对象数据类型(object),则须要将它转化为基础类型(primitive),即字符串、数字或者布尔
    1. 若是对象是Date类型,那么调用toString()方法
    2. 不然优先调用 valueOf() 方法
    3. 若是valueof()方法不存在或者并无返回一个基础类型,那么调用toString()
    4. 当数组转化为基础类型时,JavaScript 会使用join(',')方法
    5. 单纯的 Javascript 对象 {} 转化的结果是 [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)。由于篇幅关系在这里咱们简单的针对题目聊聊后者

若是==操做符的操做数的数据类型不一样:

  1. 若是一个操做数是null,而且另一个操做数是undefined,他们是相等的
  2. 若是一个操做数是数值类型,而且另外一个是字符串类型,那么把字符串类型转化为数值类型再进行比较
  3. 若是一个操做数是布尔类型,那么把true转化为1,false转化为0在进行比较
  4. 若是一个操做数是对象,另外一个操做数是数字或者字符串,那么把对象转化为基本类型再进行比较
  • 根据以上规则,在计算表达式'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,由于falsenull都能被成功转化为数字0, 因此对于isNaN来讲,它们是数字

结束

最后咱们以表达式[[][[]]+[]][+[]][++[+[]][+[]]]做为文章的结尾

在这个表达式中出现了三种操做符,分别是

  • 成员操做符: []
  • 一元操做符: +
  • 做为求和或者链接字符串做用的操做符: +
  • 自增操做符: ++

根据操做符的优先次序表,咱们能肯定操做符的优先级依次是: [] > 一元操做符+ > ++ > +

因此根据优先级咱们首先能够计算出表达式的+[]部分,而且将表达式的这一部分用计算结果替换掉: [[][[]]+[]][0][++[0][0]]

接下来咱们把表达式拆分为三部分看待: [ [][[]]+[] ] [0] [ ++[0][0] ]。若是仍是不清晰的话,三部分从左到右分别是:

  1. [ [][[]]+[] ]
  2. [0]
  3. [ ++[0][0] ]

咱们先看第一部分中+前面的 [][[]] 操做数,第一个[]是空数组,而紧跟着的[[]]是属性访问器(成员操做符),属性访问器内的[]会被强制转化为字符串类型,最终的结果便是空字符串'',因此第一个操做数的最终结果实际上是[][''],便是undefined,而又由于+操做符的规则,最终[][[]]+[]表达式的结果是字符串'undefined',那么现阶段表达式的结果是['undefined'][0][++[0][0]],即'undefined'[++[0][0]]

接下来咱们解决第三部分: [++[0][0]],我已经知道成员操做符[]的优先级要高于自增操做符++, 因此关于表达式++[0][0],咱们须要首先计算[0][0],结果是0,以后计算++0的结果便是1

因此最终表达式转化为了'undefined'[1],最终的结果便是'n'

本文也同时发布在个人知乎专栏前端技术漫游指南 上,欢迎你们关注

参考文章

相关文章
相关标签/搜索