有趣又棘手的 JavaScript 示例列表
JavaScript 是一门很棒的语言。它有简单的语法、庞大的生态系统,最重要的是,有一个很棒的社区。
同时,我们都知道 JavaScript 是一种非常有趣的语言,也有一些棘手的部分。其中有些可以很快让我们的日常工作变成地狱,有些可以让我们开怀大笑。
WTFJS 的最初想法属于 Brian Leroux。此列表深受他在 dotJS 2012 上的演讲“WTFJS”的启发:
您可以使用npm
安装本手册。只需运行:
$ npm install -g wtfjs
您现在应该可以在命令行中运行wtfjs
。这将在您选择的$PAGER
中打开手册。否则,您可以继续阅读此处。
来源可在这里找到:https://github.com/denysdovhan/wtfjs
目前, wtfjs有以下翻译:
帮助翻译成您的语言
注意:翻译由翻译人员维护。它们可能不包含所有示例,并且现有示例可能已经过时。
[]
相等![]
true
不等于![]
,但也不等于[]
NaN
不是NaN
Object.is()
和===
奇怪的情况[]
是真的,但不是true
null
是 false,但不是false
document.all
是一个对象,但它是未定义的undefined
和Number
parseInt
是个坏人true
有false
NaN
是[]
和null
是对象0.1 + 0.2
String
的实例constructor
属性__proto__
访问原型`${{Object}}`
try..catch
arguments
和箭头函数Number.toFixed()
显示不同的数字Math.max()
小于Math.min()
null
和0
{}{}
未定义arguments
绑定alert
setTimeout
对象true
的非严格比较只是为了好玩
—— 《只是为了好玩:一个偶然的革命者的故事》 ,莱纳斯·托瓦兹
该列表的主要目标是收集一些疯狂的示例,并在可能的情况下解释它们的工作原理。只是因为学习我们以前不知道的东西很有趣。
如果您是初学者,可以使用这些注释来更深入地了解 JavaScript。我希望这些注释能够激励您花更多时间阅读规范。
如果您是一名专业开发人员,您可以将这些示例视为了解我们喜爱的 JavaScript 的所有怪癖和意想不到的优势的绝佳参考。
无论如何,请阅读本文。您可能会发现新的东西。
️ 注意:如果您喜欢阅读本文档,请考虑支持该集合的作者。
// ->
用于显示表达式的结果。例如:
1 + 1 ; // -> 2
// >
表示console.log
或其他输出的结果。例如:
console . log ( "hello, world!" ) ; // > hello, world!
//
只是用于解释的注释。例子:
// Assigning a function to foo constant
const foo = function ( ) { } ;
[]
相等![]
数组等于而不是数组:
[ ] == ! [ ] ; // -> true
抽象相等运算符将两边都转换为数字来比较,两边由于不同的原因都变成数字0
。数组是 true ,因此在右侧, true 值的相反值是false
,然后将其强制为0
。然而,在左边,一个空数组被强制转换为一个数字,而没有首先变成布尔值,并且空数组被强制转换为0
,尽管它是真的。
以下是该表达式的简化方式:
+ [ ] == + ! [ ] ;
0 == + false ;
0 == 0 ;
true ;
另请参见[]
是 true ,但不是true
。
!
)true
不等于![]
,但也不等于[]
Array is not equal true
,但不是 Array is not equal true
; Array is equal false
,而不是 Array is equal false
:
true == [ ] ; // -> false
true == ! [ ] ; // -> false
false == [ ] ; // -> true
false == ! [ ] ; // -> true
true == [ ] ; // -> false
true == ! [ ] ; // -> false
// According to the specification
true == [ ] ; // -> false
toNumber ( true ) ; // -> 1
toNumber ( [ ] ) ; // -> 0
1 == 0 ; // -> false
true == ! [ ] ; // -> false
! [ ] ; // -> false
true == false ; // -> false
false == [ ] ; // -> true
false == ! [ ] ; // -> true
// According to the specification
false == [ ] ; // -> true
toNumber ( false ) ; // -> 0
toNumber ( [ ] ) ; // -> 0
0 == 0 ; // -> true
false == ! [ ] ; // -> true
! [ ] ; // -> false
false == false ; // -> true
! ! "false" == ! ! "true" ; // -> true
! ! "false" === ! ! "true" ; // -> true
逐步考虑这个:
// true is 'truthy' and represented by value 1 (number), 'true' in string form is NaN.
true == "true" ; // -> false
false == "false" ; // -> false
// 'false' is not the empty string, so it's a truthy value
! ! "false" ; // -> true
! ! "true" ; // -> true
"b" + "a" + + "a" + "a" ; // -> 'baNaNa'
这是 JavaScript 中的一个老派笑话,但经过了重新设计。这是原来的:
"foo" + + "bar" ; // -> 'fooNaN'
该表达式的计算结果为'foo' + (+'bar')
,它将'bar'
转换为非数字。
+
)NaN
不是NaN
NaN === NaN ; // -> false
该规范严格定义了此行为背后的逻辑:
- 如果
Type(x)
与Type(y)
不同,则返回false 。- 如果
Type(x)
是 Number,则
- 如果
x
为NaN ,则返回false 。- 如果
y
为NaN ,则返回false 。- …………
— 7.2.14严格相等比较
遵循 IEEE 的NaN
定义:
可能存在四种互斥关系:小于、等于、大于和无序。当至少一个操作数为 NaN 时,就会出现最后一种情况。每个 NaN 都应无序地与所有内容(包括其自身)进行比较。
—“对于 IEEE754 NaN 值,所有比较都返回 false 的基本原理是什么?”在 StackOverflow
Object.is()
和===
奇怪的情况Object.is()
确定两个值是否具有相同的值。它的工作原理与===
运算符类似,但有一些奇怪的情况:
Object . is ( NaN , NaN ) ; // -> true
NaN === NaN ; // -> false
Object . is ( - 0 , 0 ) ; // -> false
- 0 === 0 ; // -> true
Object . is ( NaN , 0 / 0 ) ; // -> true
NaN === 0 / 0 ; // -> false
在 JavaScript 术语中, NaN
和NaN
是相同的值,但它们并不严格相等。 NaN === NaN
为 false 显然是由于历史原因造成的,因此最好按原样接受它。
同样, -0
和0
严格相等,但它们不是相同的值。
有关NaN === NaN
的更多详细信息,请参阅上述案例。
你不会相信,但是……
( ! [ ] + [ ] ) [ + [ ] ] +
( ! [ ] + [ ] ) [ + ! + [ ] ] +
( [ ! [ ] ] + [ ] [ [ ] ] ) [ + ! + [ ] + [ + [ ] ] ] +
( ! [ ] + [ ] ) [ ! + [ ] + ! + [ ] ] ;
// -> 'fail'
通过将大量符号分解成碎片,我们注意到以下模式经常出现:
! [ ] + [ ] ; // -> 'false'
! [ ] ; // -> false
所以我们尝试将[]
添加到false
。但由于大量内部函数调用( binary + Operator
-> ToPrimitive
-> [[DefaultValue]]
),我们最终将正确的操作数转换为字符串:
! [ ] + [ ] . toString ( ) ; // 'false'
将字符串视为数组,我们可以通过[0]
访问它的第一个字符:
"false" [ 0 ] ; // -> 'f'
其余的很明显,但i
很棘手。通过生成字符串'falseundefined'
并抓取索引['10']
上的元素来抓取fail
中的i
。
更多示例:
+ ! [ ] // -> 0
+ ! ! [ ] // -> 1
! ! [ ] // -> true
! [ ] // -> false
[ ] [ [ ] ] // -> undefined
+ ! ! [ ] / + ! [ ] // -> Infinity
[ ] + { } // -> "[object Object]"
+ { } // -> NaN
[]
是真的,但不是true
数组是一个真值,但是它不等于true
。
! ! [ ] // -> true
[ ] == true // -> false
以下是 ECMA-262 规范中相应部分的链接:
!
)null
是 false,但不是false
尽管null
是一个假值,但它不等于false
。
! ! null ; // -> false
null == false ; // -> false
同时,其他虚假值,如0
或''
等于false
。
0 == false ; // -> true
"" == false ; // -> true
解释与前面的示例相同。这是相应的链接:
document.all
是一个对象,但它是未定义的
️ 这是浏览器 API 的一部分,无法在 Node.js 环境中工作️
尽管document.all
是一个类似数组的对象,并且它可以访问页面中的 DOM 节点,但它对typeof
函数的响应为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
)而进步时,此 API 调用就变得过时了,标准委员会必须决定如何处理它。由于其广泛使用,他们决定保留 API,但故意违反 JavaScript 规范。使用undefined
的严格相等比较时它响应false
,而使用抽象相等比较时响应true
原因是由于故意违反明确允许这样做的规范。— WhatWG - HTML 规范中的“过时功能 - document.all” — YDKJS - 类型和语法中的“第 4 章 - ToBoolean - Falsy 值”
Number.MIN_VALUE
是最小的数字,大于零:
Number . MIN_VALUE > 0 ; // -> true
Number.MIN_VALUE
是5e-324
,即可以在浮点精度内表示的最小正数,即尽可能接近零。它定义了浮动可以为您提供的最佳分辨率。现在总体最小值是
Number.NEGATIVE_INFINITY
尽管严格意义上它并不是真正的数字。— “为什么 JavaScript 中
0
小于Number.MIN_VALUE
?”在 StackOverflow
️ V8 v5.5 或更低版本中存在的错误 (Node.js <=7)️
大家都知道烦人的undefined is not a function ,但是这个呢?
// Declare a class which extends null
class Foo extends null { }
// -> [Function: Foo]
new Foo ( ) instanceof null ;
// > TypeError: function is not a function
// > at … … …
这不是规范的一部分。这只是一个错误,现在已经修复了,所以将来应该不会有问题。
这是现代环境中先前错误的故事的延续(使用 Chrome 71 和 Node.js v11.8.0 进行测试)。
class Foo extends null { }
new Foo ( ) instanceof null ;
// > TypeError: Super constructor null of Foo is not a constructor
这不是一个错误,因为:
Object . getPrototypeOf ( Foo . prototype ) ; // -> null
如果类没有构造函数,则从原型链调用。但在父级中没有构造函数。为了以防万一,我将澄清null
是一个对象:
typeof null === "object" ;
因此,您可以继承它(尽管在面向对象编程的世界中,这样的术语可能会打败我)。所以你不能调用 null 构造函数。如果您更改此代码:
class Foo extends null {
constructor ( ) {
console . log ( "something" ) ;
}
}
您会看到错误:
ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
如果你添加super
:
class Foo extends null {
constructor ( ) {
console . log ( 111 ) ;
super ( ) ;
}
}
JS 抛出错误:
TypeError: Super constructor null of Foo is not a constructor
如果您尝试添加两个数组怎么办?
[ 1 , 2 , 3 ] + [ 4 , 5 , 6 ] ; // -> '1,2,34,5,6'
连接发生了。一步一步,它看起来像这样:
[ 1 , 2 , 3 ] +
[ 4 , 5 , 6 ] [
// call toString()
( 1 , 2 , 3 )
] . toString ( ) +
[ 4 , 5 , 6 ] . toString ( ) ;
// concatenation
"1,2,3" + "4,5,6" ;
// ->
( "1,2,34,5,6" ) ;
您创建了一个包含 4 个空元素的数组。尽管如此,由于尾随逗号,您将得到一个包含三个元素的数组:
let a = [ , , , ] ;
a . length ; // -> 3
a . toString ( ) ; // -> ',,'
在向 JavaScript 代码添加新元素、参数或属性时,尾随逗号(有时称为“最后的逗号”)非常有用。如果要添加新属性,只需添加一个新行,而无需修改之前的最后一行(如果该行已使用尾随逗号)。这使得版本控制差异更清晰,并且编辑代码可能不那么麻烦。
— MDN 中的尾随逗号
数组相等是 JS 中的一个怪物,如下所示:
[ ] == '' // -> true
[ ] == 0 // -> true
[ '' ] == '' // -> true
[ 0 ] == 0 // -> true
[ 0 ] == '' // -> false
[ '' ] == 0 // -> true
[ null ] == '' // true
[ null ] == 0 // true
[ undefined ] == '' // true
[ undefined ] == 0 // true
[ [ ] ] == 0 // true
[ [ ] ] == '' // true
[ [ [ [ [ [ ] ] ] ] ] ] == '' // true
[ [ [ [ [ [ ] ] ] ] ] ] == 0 // true
[ [ [ [ [ [ null ] ] ] ] ] ] == 0 // true
[ [ [ [ [ [ null ] ] ] ] ] ] == '' // true
[ [ [ [ [ [ undefined ] ] ] ] ] ] == 0 // true
[ [ [ [ [ [ undefined ] ] ] ] ] ] == '' // true
你应该非常仔细地观察上面的例子!该行为在规范的第7.2.15节“抽象相等比较”中进行了描述。
undefined
和Number
如果我们不将任何参数传递给Number
构造函数,我们将得到0
。当没有实际参数时,值undefined
被分配给形式参数,因此您可能期望不带参数的Number
将undefined
作为其参数的值。然而,当我们传递undefined
时,我们将得到NaN
。
Number ( ) ; // -> 0
Number ( undefined ) ; // -> NaN
根据规范:
n
为+0
。n
为 ? ToNumber(value)
。undefined
, ToNumber(undefined)
应返回NaN
。这是相应的部分:
argument
) parseInt
是个坏人parseInt
因其怪癖而闻名:
parseInt ( "f*ck" ) ; // -> NaN
parseInt ( "f*ck" , 16 ) ; // -> 15
解释:发生这种情况是因为parseInt
将继续逐个字符地解析,直到遇到它不知道的字符。 'f*ck'
中的f
是十六进制数字15
。
将Infinity
解析为整数是……
//
parseInt ( "Infinity" , 10 ) ; // -> NaN
// ...
parseInt ( "Infinity" , 18 ) ; // -> NaN...
parseInt ( "Infinity" , 19 ) ; // -> 18
// ...
parseInt ( "Infinity" , 23 ) ; // -> 18...
parseInt ( "Infinity" , 24 ) ; // -> 151176378
// ...
parseInt ( "Infinity" , 29 ) ; // -> 385849803
parseInt ( "Infinity" , 30 ) ; // -> 13693557269
// ...
parseInt ( "Infinity" , 34 ) ; // -> 28872273981
parseInt ( "Infinity" , 35 ) ; // -> 1201203301724
parseInt ( "Infinity" , 36 ) ; // -> 1461559270678...
parseInt ( "Infinity" , 37 ) ; // -> NaN
解析null
时也要小心:
parseInt ( null , 24 ) ; // -> 23
解释:
它将
null
转换为字符串"null"
并尝试转换它。对于基数 0 到 23,它没有可以转换的数字,因此它返回 NaN。在 24 处,第 14 个字母"n"
被添加到数字系统中。在 31 处,添加第 21 个字母"u"
,并且可以解码整个字符串。在 37 处,不再可以生成任何有效的数字集,并且返回NaN
。—“parseInt(null, 24) === 23…等等,什么?”在 StackOverflow
不要忘记八进制:
parseInt ( "06"