有趣又棘手的 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"