什么鬼 JS
第二季
前戏
在学一些 JS 里面就有提到过 JS 的常见问题 [滑稽]。最近又看到了类似的总结。前戏到此为止,马上进入吧。
true 等于 false
1 | [] == ![] // -> true |
[]
是 JavaScript 里面的数组(Array)。也就是数组不等于数组!
要想知道为什么,当然是看语言规范啦!(不要再吭哧吭哧地扣汇编代码)
因为非运算符 !
比相等判别运算符==
的优先级高,所以先看非运算符的规范
12.5.9Logical NOT Operator (!)
12.5.9.1Runtime Semantics: Evaluation
- Let expr be the result of evaluating UnaryExpression.
- Let oldValue be ToBoolean(? GetValue(expr)).
- If oldValue is true, return false.
- Return true.
其中最关键的一步是第二条的 ToBoolean(![])
。再看 ToBoolean
的规范。
7.1.2ToBoolean ( argument )
The abstract operation ToBoolean converts argument to a value of type Boolean according to Table 9:
Argument Type Result Undefined Return false. Null Return false. Boolean Return argument. Number If argument is +0, -0, or NaN, return false; otherwise return true. String If argument is the empty String (its length is zero), return false; otherwise return true. Symbol Return true. Object Return true.
[]
虽然是数组,但是 typeof []
是 Object 。所以查表 ![]
的结果为 true
。
结果转化为 [] == true
。
再看 ==
相等判别运算符的规范
7.2.13Abstract Equality Comparison
The comparison x == y, where x and y are values, produces true or false. Such a comparison is performed as follows:
- If Type(x) is the same as Type(y), thenReturn the result of performing Strict Equality Comparison x === y.
- Return the result of performing Strict Equality Comparison x === y.
- 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, Number, or Symbol 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, Number, or Symbol, return the result of the comparison ToPrimitive(x) == y.
- Return false.
显然 [] == true
对应的是第 7 条。
再看 ToNumber 的规范
7.1.3ToNumber ( argument )
The abstract operation ToNumber converts argument to a value of type Number according to Table 10:
Argument Type Result Undefined Return NaN. Null Return +0. Boolean If argument is true, return 1. If argument is false, return +0. Number Return argument (no conversion). String See grammar and conversion algorithm below. Symbol Throw a TypeError exception. Object Apply the following steps:Let primValue be ? ToPrimitive(argument, hint Number).Return ? ToNumber(primValue).
true
的类型是 Boolean , 所以结果是 1 。
结果又转化为 [] == 1
在看相等判断运算符的规范,这时候符合第九条。 要运算 ToPrimitive([]) == 1
查看 ToPrimitive 的规范即可。这里有个偷懒的办法。因为 ToBoolean([]) == true
。
所以 true == 1
的结果是 true
。
baNaNa
1 | 'b' + 'a' + + 'a' + 'a' // => baNaNa |
这里其实耍了个花招,实际上是这样的。
1 | 'b' + 'a' + (+ 'a') + 'a' |
因为 +'a'
是 NaN
。所以整个式子的结果就是 baNaNa 。
NaN 不是 NaN
1 | NaN === NaN // -> false |
这个看严格判等运算符 ===
的规范就能很容易得到答案
7.2.14Strict Equality Comparison
The comparison x === y, where x and y are values, produces true or false. Such a comparison is performed as follows:
- If Type(x) is different from Type(y), return false.
- 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.
- Return SameValueNonNumber(x, y).
所以根据 2.1 ,结果是 false
。
因为 IEEE 是这样定义 NaN
四种互斥的关系是:小于,等于,大于以及无序。最后一种情况出现在至少有一个操作符为 NaN 的情况。NaN 与所有相比的结果都应该是无序,包括自身。
— “What is the rationale for all comparisons returning false for IEEE754 NaN values?” at StackOverflow
##NaN 是 number
1 | typeof NaN // -> 'number' |
这样看似不合理。实际上,稍微思考一下就觉得是非常自然的。
比如有一个除法函数。
1 | function div(x, y) |
div
的返回类型当然是 number
。所以当 div(0, 0)
产生结果为 NaN
时,如果 NaN
不是 number
类型就比较难办了。
参考 typeof 运算符规范
最小值大于零
1 | Number.MIN_VALUE > 0 // -> true |
因为 Number.MIN_VALUE
的值为 5e-324,也就是 $5 \times 10^{-324}$ ,所以当然是大于 0 的啊。
这里的最小值想要表达的是浮点数的最小精度。
HTML 注释在 JS 是合法的
1 | // valid comment |
没想到吧!JS 中也可以使用 HTML 式的注释方式。而且这是写在规范里面的。
疯狂打 CALL
1 | console.log.call.call.call.call.call.apply(a => a, [1, 2]) // => 2 |
熟悉 call 和 apply 的用法就能很快看出
1 | console.log.call.call.call.call.call.apply(a => a, [1, 2]) |
事后
以上所有例子出自于 WTFJS,里面还有很多跟新语法有关的例子。