译者按: JavaScript有很多坑,经常一不小心就要写bug。
为了保证可读性,本文采用意译而非直译。另外,本文版权归原作者所有,翻译仅用于学习。
JavaScript是一门伟大的语言,它拥有非常简洁的语法,庞大的生态系统,以及最重要的:有一个伟大的社区支撑着。同时,我们也知道JavaScript是一个充满技巧性的语言。有些坑足以让我们崩溃,也有些奇淫技巧让我们觉得很有趣。本文的思想源自于Brian Leroux在dotJS2012上的演讲“WTFJS” at dotJS 2012。
我收集这些例子的主要目的是将它们整理并清楚理解它们的原理。从中学到很多以前不懂的知识是一件很有趣的事情。如果你是初学者,你可以通过学习这些笔记深入理解JavaScript;如果你是一个专业的开发者,那么可以将这些笔记作为一个不错的引用资料。不管怎样,只要读下去,你就会学到新东西的。
[ ] == ![ ] // -> true
相等(==)判断操作会将两边的类型都转换为数字(number),然后再比较。因为`[]`和`![]`都会转换为`0`。我们可以理解`[]`是一个数组,只不过为空而已,那么为true。右侧`![]`则为false。false然后转换为数字0。左侧`[]`直接转换为数字,因为空数组会转换为`0`,所以尽管我们认为`[]`为true,这里却变成了`0`。 下面是简化的计算过程:
+[] == +![]
0 == +false
0 == 0
true
参考: - [12.5.9 Logical NOT Operator (!)](https://www.ecma-international.org/ecma-262/#sec-logical-not-operator) - [7.2.13 Abstract Equality Comparison](https://www.ecma-international.org/ecma-262/#sec-abstract-equality-comparison) ## true 是 false
!!'false' == !!'true' // -> true
!!'false' === !!'true' // -> true
true是一个真值,用1表示;字符串的`“true”`则为NaN。
true == 'true' // -> false
false == 'false' // -> false
‘false’是一个有意义的字符串。
!!'false' // -> true
!!'true' // -> true
参考:[7.2.13 Abstract Equality Comparison](https://www.ecma-international.org/ecma-262/#sec-abstract-equality-comparison) ## baNaNa
'b' + 'a' + + 'a' + 'a' // -> baNaNa
这是一个旧笑话,不过改进过的。原始的长这样:
'foo' + + 'bar' // -> 'fooNaN'
该表达式以`’foo’ + (+’bar’)`的形式计算,因为`bar`不是数字,所以转换为NaN。 参考: - [12.8.3 The Addition Operator (+)](https://www.ecma-international.org/ecma-262/#sec-addition-operator-plus) - [12.5.6 Unary + Operator](https://www.ecma-international.org/ecma-262/#sec-unary-plus-operator) ## NaN不等于NaN
NaN === NaN // -> false
根据`===`的算法,我们可以容易理解为什么为false。 > 如果typeof(x)和typeof(y)不同,那么返回false. > 否则,如果typeof(x)是Number,那么 > 1. 如果x是NaN,那么返回false; > 2. 如果y是NaN,那么返回false; > 3. … 由此可以得出值为false的结论。 ## fail
(![]+[])[+[]]+(![]+[])[+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]] // -> fail
如果我们仔细观察序列的规律,会发现下面的模式出现很多次:
(![]+[]) // -> 'false'
![] // -> false
因此,我们尝试将[]和false相加。但是根据内部一些列函数的计算(binary + Operator -> ToPrimitive -> [[DefaultValue]]),右侧的[]最终转换为string:
(![]+[].toString()) // 'false'
对于一个字符串,我们就可以通过下标来获取对应的字符:
'false'[0] // -> 'f'
剩下的都很直观,除了`i`很取巧。`fail`中的`i`是通过在`falseundefined`中获取第十个下标对应的字符而得到。 ## []包含值,但不是true 空数组不等于true。(An array is a truthy value, however, it’s not equal to true.)
!![] // -> true
[] == true // -> false
参考: - [12.5.9 Logical NOT Operator (!)](https://www.ecma-international.org/ecma-262/#sec-logical-not-operator) - [7.2.13 Abstract Equality Comparison](https://www.ecma-international.org/ecma-262/#sec-abstract-equality-comparison) ## null不等于false 尽管null是一个false的值,但是null不等于false。
!!null // -> false
null == false // -> false
不过,如果和其它false的值比较,那么他们又是相等的。
0 == false // -> true
'' == false // -> true
参考: [7.2.13 Abstract Equality Comparison](https://www.ecma-international.org/ecma-262/#sec-abstract-equality-comparison) JavaScript坑很多,赶紧使用[fundebug](https://www.fundebug.com)扶一扶! ## document.all是一个对象,不过是undefined ⚠️这个是前端浏览器API,在Nodejs环境无法使用。 尽管document.all可以返回一个像数组一样的对象,可以用来访问DOM节点。但是呢,通过typeof查看document.all,你会惊讶地发现类型是`undefined`。
document.all instanceof Object // -> true
typeof document.all // -> 'undefined'
而且,document.all并不等于undefined。
document.all === undefined // -> false
document.all === null // -> false
而且,更惊讶的是:
document.all == null // -> true
document.all是一个过去常用的获取DOM元素的方法,特别是老版本的IE。但是从未进入标准,尽管广泛使用在过去的JS代码中。当新的API突出来(比如document.getElementById)后,document.all就被淘汰了。标准委员会不得不觉得怎么处理它。可是因为它已经被广泛使用,所以委员会觉得保留它,但是违背了JavaScript的规范。 参考: - [ Obsolete features - document.all](https://html.spec.whatwg.org/multipage/obsolete.html#dom-document-all) - [ Chapter 4 - ToBoolean - Falsy values](https://github.com/getify/You-Dont-Know-JS/blob/0d79079b61dad953bbfde817a5893a49f7e889fb/types%20%26%20grammar/ch4.md#falsy-objects) ## 最小值比0还大 `Number.MIN_VALUE`是最小的数,但是它比0还大。
Number.MIN_VALUE > 0 // -> true
因为Number.MIN_VALUE
是5e-324
。也就是说即使最小的值也可以用浮点数表示出来,虽然离0很接近,但是依然比0大。其实最小的数是Number.NEGATIVE_INFINITY
,尽管它不是一个实际存在的数。
在StackOverflow有相关问题:Why is 0 less than Number.MIN_VALUE in JavaScript?。
参考: 20.1.2.9 Number.MIN_VALUE