诡异的操作符使用总结

看过了程序员送给产品经理的一段代码:

(!(~+[]) + {})[--[~+""][+[]] * [~+[]] + ~~!+[]] + ({} + [])[[~!+[]] * ~+[]]

又看了一些类似的奇葩面试题:

[]+[]
{}+{}
1+[]

心里觉得这些东西看着挺奇葩,但是里面的知识还是挺有意思的。所以做了找了点资料,总结了一下。

数据类型

  • 原始数据类型:string,number,boolean,undefined,null,symbol
  • 复杂数据类型:array,object,set,map等…

隐式转换

不同的操作符会将操作数转换成基本数据类型。使用 ToPrimitive(),它根据上下文环境,将复杂数据类型转换为原始数据类型。

ToPrimitive(input, PreferredType)
  • input: 输入的值。
  • PreferredType 是可选参数。只接受 Number 和 String。决定复杂数据类型优先转换成哪种数据类型。
input的类型 操作
Null 不转换,直接返回
Undefined 不转换,直接返回
Number 不转换,直接返回
Boolean 不转换,直接返回
String 不转换,直接返回
Symbol 不转换,直接返回
Object 转换后返回

简单来说

PreferredType 为 Number
  1. input为原始类型,直接返回。
  2. Input 为对象,调用 valueOf() 方法,若结果为原始类型,返回。
  3. 如果 2 步返回的依然是对象,则调用 toString() 方法,若结果为原始数据类型,返回。
  4. 如果仍然不是原始数据类型,则抛出异常 Uncaught TypeError: Cannot convert object to primitive value。
PreferredType 为 String
  1. input为原始类型,直接返回。
  2. Input 为对象,调用 toString() 方法,若结果为原始类型,返回。
  3. 如果 2 步返回的依然是对象,则调用 valueOf() 方法,若结果为原始数据类型,返回。
  4. 如果仍然不是原始数据类型,则抛出异常 Uncaught TypeError: Cannot convert object to primitive value。

运算符

+ 运算

+运算在 JavaScript 中有三个作用:

  1. 连接字符串:var result = ‘Shuai’ + ‘XUE’
  2. 计算数字之和: var result = 1 + 2
  3. 作为一元操作符:+variable

作为一元操作符的运算规则就比较简单,就是把操作数从任意类型转化为数字类型做运算。

当操作数类型不一致的时候,按照以下规则进行转化:

  • 如果至少一个操作数类型是 object,则需要使用 ToPrimitive 方法转化为基本类型。

    1. 如果object是 Date 类型,调用 toString() 方法
    2. 除 Date 以外的其他类型,首先调用 valueof() 方法
    3. 如果 valueof() 方法不存在,或者返回值不是基本类型值,调用 toString()
    4. 数组转化为基础类型时,默认使用 join(‘,’) 方法
    5. 单纯(没有改写过相应的原型方法)的 object 转化的结果是 [object Object]
  • 转化为基本类型值后,如果有一个以上的操作数是 string 型,那么 + 运算符的操作是执行字符串拼接操作。就是把其余的所有操作数全部转换为字符串类型拼接在一起。

  • 在其他情况下,也就是操作数中没有 object 型 和 string 型,将操作数转化为 number 型,执行加法操作.
  • 需要注意的是 {}+[],开头的 {} 会被认为是空代码块,所以这个其实是求 +[] 的值.
  • 在 Chrome 中,执行代码会隐式的添加 (),在 IE 中,却不会这使得一些值的计算会略有差异。比如:{}+{}.
- 运算

-运算只有一个功能,就是数值上的相减。所有的操作数都转化为数值类型,然后做减法运算。转化结果如果是 NaN,那么表达式结果也会是 NaN。

==操作符

在 JavaScript 中 === 被称为是恒等操作符,它会同时比较操作数的值和类型。

而 == 则不同,操作数的数据类型不同,会发生隐式转换,然后再比较的过程:

  1. nullundefined 是相等的。
  2. 如果有一个操作数是 number 类型,另一个是 string 类型,则把 string 类型操作数转换为数值类型,在进行比较。
  3. boolean 的 true 转化为 1,false 转化为 0,进行比较
  4. 如果一个操作数是 string 或 number 类型,另一个操作数是 object 类型,那么把 object 转化为基本类型再进行比较。
  5. NaN 不等于任何值,包括他自己
< 和 >作符

遵循和 == 相同的规则,多个堆叠,从左往右依次比较。如 1 > 2 > 3

isNaN

NaN 是 “Not a Number” 的缩写。这里要明确的是 isNaN() 方法并不能够判断是否是数字类型。首先他会强制将参数转化为数值类型,再进行判断。根据上面我们提到的,true 会转化为 1,false 会转化为 0,更多地,null 会转化为0. 所以 isNaN(false) 返回的是 true

下面是一些题目可供练习:

  • 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)
  • [[][[]]+[]][+[]][++[+[]][+[]]]
  • (!(~+[]) + {})[–[~+””][+[]] [~+[]] + ~~!+[]] + ({} + [])[[~!+[]] ~+[]]