面白くてトリッキーな 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
になります。配列は真実であるため、右側では、真実の値の反対はfalse
であり、これは0
に強制されます。ただし、左側では、空の配列は最初にブール値になることなく数値に強制され、空の配列は真であるにもかかわらず0
に強制されます。
この式を簡略化すると次のようになります。
+ [ ] == + ! [ ] ;
0 == + false ;
0 == 0 ;
true ;
[]
真実ですが、 true
ではありませんも参照してください。
!
)true
は![]
と等しくありませんが、 []
も等しくありません「配列が等しくないtrue
ですが、「配列が等しくない」もtrue
。配列はfalse
に等しいですが、配列も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
の定義に従います。
相互に排他的な 4 つの関係 (未満、等しい、以上、および順序なし) が可能です。最後のケースは、少なくとも 1 つのオペランドが NaN である場合に発生します。すべての NaN は、それ自体を含むすべてのものと順序なしで比較されます。
— 「IEEE754 NaN 値に対してすべての比較が false を返す根拠は何ですか?」スタックオーバーフローで
Object.is()
と===
奇妙なケースObject.is()
2 つの値が同じ値を持つかどうかを判断します。これは===
演算子と同様に機能しますが、奇妙なケースがいくつかあります。
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
は注意が必要です。 fail
たi
は、文字列'falseundefined'
を生成し、インデックス['10']
の要素を取得することによって取得されます。
その他の例:
+ ! [ ] // -> 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 値は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
、特に古いバージョンの IE で DOM 要素にアクセスする方法でした。これは決して標準ではありませんが、古い時代の JS コードで広く使用されていました。標準が新しい API (document.getElementById
など) で発展すると、この API 呼び出しは廃止され、標準委員会はこれをどうするかを決定する必要がありました。その広範な用途のため、彼らは API を維持することにしましたが、JavaScript 仕様の意図的な違反を導入しました。厳密な等価比較を使用する場合はundefined
でfalse
を返し、抽象的な等価比較を使用する場合はtrue
を返す理由は、それを明示的に許可する仕様に意図的に違反しているためです。— 「廃止された機能 - document.all」(WhatWG - HTML 仕様) — 「第 4 章 - ToBoolean - Falsy 値」(YDKJS - 型と文法)
Number.MIN_VALUE
はゼロより大きい最小の数値です。
Number . MIN_VALUE > 0 ; // -> true
Number.MIN_VALUE
は5e-324
です。つまり、float 精度内で表現できる最小の正の数、つまり、ゼロに限りなく近い値です。これは、float が提供できる最高の解像度を定義します。全体の最小値は
Number.NEGATIVE_INFINITY
ですが、厳密な意味では数値ではありません。— 「JavaScript では、なぜ
0
Number.MIN_VALUE
より小さいのですか?」スタックオーバーフローで
️ V8 v5.5 以前に存在するバグ (Node.js <=7)️
皆さんは迷惑なunknown 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" ;
したがって、それを継承することができます (ただし、OOP の世界ではそのような用語を使用すると私は負けるでしょう)。したがって、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
2 つの配列を追加しようとするとどうなるでしょうか?
[ 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 つの空の要素を含む配列が作成されました。それでも、末尾にカンマがあるため、3 つの要素を含む配列が得られます。
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
未知の文字に到達するまで 1 文字ずつ解析を続けるために発生します。 'f*ck'
のf
16 進数の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… 待って、何?」スタックオーバーフローで
8 進数についても忘れないでください。
parseInt ( "06"