以下はWhat the f*ck JavaScript?というリポジトリの日本語訳です。
What the f*ck JavaScript?
JavaScriptは素晴らしい言語です。
単純な構文、大きなエコシステム、そして最も重要なところはコミュニティです。
同時に、JavaScriptは非常にトリッキーで面倒な言語でもあります。
幾つかの仕様は我々の仕事を地獄に変え、他の幾つかは笑える仕様です。
WTFJSの大元のアイデアはBrian Lerouxによるものです。
以下のリストは2012年のdotJSにおける彼のトーク、WTFJSに触発されています。
Node Packaged Manuscript
以下の内容はnpmでインストールできます。
$ npm install -g wtfjs
wtfjs
コマンドで読めるようになるはずです。
駄目だったらここで読みましょう。
Table of Contents
Motivation
面白半分だ。
Just for Fun: The Story of an Accidental Revolutionary, Linus Torvalds
このリストの主な目的は、バグった実例を収集し、可能であればどうしてそうなっているのかを解説することです。
これまで知らなかったことを学ぶのは楽しいでしょう。
初心者は、これらを通じてJavaScriptをより深く理解することができます。
これらを見ているときっと仕様書を読みたくなることでしょう。
プロの開発者であれば、JavaScriptの持つ罠や予期せぬ挙動の参考資料として役立ててくれると思います。
いずれにせよ、以下を読んでみてください。
おそらく新しい発見があることでしょう。
Notation
// ->
は式の結果を表します。
1 + 1 // -> 2
// >
はconsole.logもしくはその他の出力内容を表します。
console.log('hello, world!') // > hello, world!
//
は解説用の単なるコメントです。
// Assigning a function to foo constant
const foo = function () {}
Examples
[] is equal ![]
ArrayはArrayとイコールではない。
[] == ![] // -> true
12.5.9 Logical NOT Operator (!)
7.2.13 Abstract Equality Comparison
true is false
trueはfalseだった。
!!'false' == !!'true' // -> true
!!'false' === !!'true' // -> true
順番に考えるとわかりやすい。
// trueは数値扱いされると1になる
true == 'true' // -> false
false == 'false' // -> false
// 'false'は空文字ではないので数値扱いされると1になる
!!'false' // -> true
!!'true' // -> true
7.2.13 Abstract Equality Comparison
baNaNa
バナナ
'b' + 'a' + + 'a' + 'a'
使い古されたジョークのリバイバルで、オリジナルは以下だ。
'foo' + + 'bar' // -> 'fooNaN'
式が'foo' + (+'bar')
と解釈され、'bar'は数値ではないので'NaN'という文字列になる。
12.8.3 The Addition Operator (+)
12.5.6 Unary + Operator
NaN is not a NaN
NaNはNaNではない。
NaN === NaN // -> false
これは仕様で厳密に決められている。
1. If Type(x) is different from Type(y), return false.
2. If Type(x) is Number, then
i. If x is NaN, return false.
ii. If y is NaN, return false.
iii. ………
7.2.14 Strict Equality Comparison
IEEEのNaNの仕様に従っている。
Four mutually exclusive relations are possible: less than, equal, greater than, and unordered. The last case arises when at least one operand is NaN. Every NaN shall compare unordered with everything, including itself.
What is the rationale for all comparisons returning false for IEEE754 NaN values?
It's a fail
信じられないかもしれないが、答えは'fail'だ。
(![]+[])[+[]]+(![]+[])[+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]
// -> 'fail'
以下のパターンが頻出することに気がつくだろう。
(![]+[]) // -> 'false'
![] // -> false
false
に[]
を加算しようとしているため、右オペランドはstringに変換される。
(![]+[].toString()) // 'false'
文字列に配列アクセスすると該当番目の文字が取得できる。
'false'[0] // -> 'f'
残りも同じ構造だが、i
の取って来かたは少しトリッキーだ。
文字列'falseundefined'を作って、その10番目を取得している。
[] is truthy, but not true
[]
はtrueっぽいけどtrueではない。
!![] // -> true
[] == true // -> false
ECMA-262で決められている仕様である。
12.5.9 Logical NOT Operator (!)
7.2.13 Abstract Equality Comparison
null is falsy, but not false
null
はfalseっぽいけどfalseではない。
!!null // -> false
null == false // -> false
ところが0や''はfalseとイコールになる。
0 == false // -> true
'' == false // -> true
これもECMA-262の仕様。
7.2.13 Abstract Equality Comparison
document.all is an object, but it's undefined
document.allは配列のようなobjectであり、DOMノードへのアクセスを提供するが、typeof
には何故かundefined
を返す。
document.all instanceof Object // -> true
typeof document.all // -> 'undefined'
しかしundefined
とイコールではない。
document.all === undefined // -> false
document.all === null // -> false
緩やかな比較ではイコールになる。
document.all == null // -> true
document.all
は主に古いバージョンのIEで使用されていたDOMへのアクセス方法で、標準仕様になったことはないが、古いJavaScriptコードではよく使用されていた。
getElementById
等の新しいAPI標準仕様が普及したのち、このdocument.all
の取り扱いについて決める必要があった。
JavaScriptの仕様には反していたのだが、広く使用されていたためAPI自体は維持することにした。
===にfalseを返し、==にtrueを返すのは、明示された例外事項である。
Minimal value is greater than zero
最小の数値は0より大きい。
Number.MIN_VALUE > 0 // -> true
Number.MIN_VALUE
の値は5e-324
であり、これは0より大きい最小の値である。
浮動小数で表せる最高の解像度を表している。
いわゆる最小値はNumber.NEGATIVE_INFINITY
として定義されているが、これは厳密には数値ではない。
function is not function
忌々しいundefinedが関数でないことは皆知っているが、では以下は?
// nullをextendsする
class Foo extends null {}
// -> [Function: Foo]
new Foo instanceof null
// > TypeError: function is not a function
これは仕様ではないから、いずれ修正されるだろう。
Adding arrays
2個の配列を加算したい。
[1, 2, 3] + [4, 5, 6] // -> '1,2,34,5,6'
文字列連結になってしまう。
[1, 2, 3] + [4, 5, 6]
// toString()が呼ばれる
[1, 2, 3].toString() + [4, 5, 6].toString()
// 結合
'1,2,3' + '4,5,6'
// ->
'1,2,34,5,6'
Trailing commas in array
空要素が4個ある配列を作成した。
let a = [,,,]
a.length // -> 3
a.toString() // -> ',,'
末尾のカンマは要素やプロパティを付け加えたいときに役に立つ。
前の行に末尾カンマが付いていた場合、その行を変更することなく追加のプロパティを書くだけで済むからだ。
これによって差分管理もわかりやすくなる。
Trailing commas
Array equality is a monster
配列の==は危険がいっぱい。
[] == '' // -> 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.13 Abstract Equality Comparison
undefined and Number
Numberのコンストラクタに引数を渡さないと、返り値は0になる。
引数のある関数に引数を渡さなかった場合、仮引数はundefinedになる。
従ってNumberにundefinedを渡すと返り値もundefinedになるだろうと思うが、実際はNaNになる。
Number() // -> 0
Number(undefined) // -> NaN
仕様によると、
・引数がなかった場合は0とする
・それ以外の場合はToNumber()を呼ぶ
・ToNumber(undefined)はNaNになる
20.1.1 The Number Constructor
7.1.3 ToNumber(argument)
parseInt is a bad guy
parseIntはバッドガイだ。
parseInt('f*ck'); // -> NaN
parseInt('f*ck', 16); // -> 15
parseIntは理解できない文字に出会うまでパースを続けるが、出会ったらエラーを出さずにそこまでの値を返してしまう。
'f*ck'の'f'は16進数で15である。
parseIntは無限大ですら整数解析してしまう。
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'に変換されてしまい、その後通常のパースが行われる。
基数23までは'n'が解析できないのでNaNが返ってくる。
基数24で'n'が理解できるようになり結果が23になる。
基数31で'u'までパースされ、結果は1112745になる。
基数を37以上にすると再びNaNになる。
parseInt(null, 24) === 23… wait, what?
8進数の罠も忘れてはいけない。
parseInt('06'); // 6
parseInt('08'); // 8 ES5
parseInt('08'); // 0 ES5未対応
引数が'0'で始まる場合、基数が8になるか10になるかは実装依存である。
ES5では10進数であることを要求しているが、全てのブラウザがそのように実装されているとは限らない。
そのため、parseIntには必ず基数を指定しなければならない。
parseInt({ toString: () => 2, valueOf: () => 1 }) // -> 2
Number({ toString: () => 2, valueOf: () => 1 }) // -> 1
Math with true and false
true + true // -> 2
(true + true) * (true + true) - true // -> 3
Numberに値を与えると数値に変換されるが、trueを与えると1になるのは自明であろう。
Number(true) // -> 1
単項演算子+
は続く値を数値に変換しようとする。
これは整数と浮動小数の文字列表現、そしてtrue、false、nullに対応している。
それ以外の値に対してはNaNになる。
つまり、+true
は1になる。
+true // -> 1
加算・乗算の際にはJavaScriptの内部関数ToNumber()が呼び出されるが、これは以下のような仕様になっている。
If argument is true, return 1. If argument is false, return +0.
そのため、真偽値を通常の数値と同じように計算することが可能になってしまうというわけだ。
12.5.6 Unary + Operator
12.8.3 The Addition Operator (+)
HTML comments are valid in JavaScript
昔懐かし、HTMLコメント<!--
はJavaScriptでも有効なコメントだ。
// valid comment
<!-- valid comment too
HTML形式コメントは、<script>
タグを理解しないブラウザで動かなくならないために作られたのものだ。
しかし、その対象のブラウザNetscape 1.x
はもはや存在しないから、HTML形式コメントを入れる意味は全く無い。
Node.jsはV8エンジンに基づいているから、HTML形式コメントについても未だに対応している。
むしろ完全に仕様の一部となっている。
NaN is a number
NaNは数値型である。
typeof NaN // -> 'number'
typeofとinstanceofの仕様は以下のとおりだ。
12.5.5 The typeof Operator
12.10.4 Runtime Semantics: InstanceofOperator(O,C)
[] and null are objects
[]
とnullはオブジェクトである。
typeof [] // -> 'object'
typeof null // -> 'object'
// しかしinstanceではない
null instanceof Object // false
typeof演算子は、対応テーブルに従って文字列を返す。
従ってcallを実装していないオブジェクト、およびnullは"object"になる。
toStringメソッドを使うと、objectの詳しい内容まで確認できる。
Object.prototype.toString.call([])
// -> '[object Array]'
Object.prototype.toString.call(new Date)
// -> '[object Date]'
Object.prototype.toString.call(null)
// -> '[object Null]'
Magically increasing numbers
999999999999999 // -> 999999999999999
9999999999999999 // -> 10000000000000000
10000000000000000 // -> 10000000000000000
10000000000000000 + 1 // -> 10000000000000000
10000000000000000 + 1.1 // -> 10000000000000002
これは浮動小数点演算の標準規格IEEE 754-2008に従った動作であり、このスケールでは最も近い偶数に丸められてしまう。
6.1.6 The Number Type
IEEE 754 on Wikipedia
Precision of 0.1 + 0.2
浮動小数演算では致命的な間違いが発生する。
0.1 + 0.2 // -> 0.30000000000000004
(0.1 + 0.2) === 0.3 // -> false
StackOverflowの質問Is floating point math broken?に理由が書いてある。
0.2や0.3という値は、実際は真の値の近似表示である。
近似値0.2の真の値は見た目の値よりも大きく、近似値0.3の真の値は見た目の値よりも小さい。
近似値0.1と0.2の和は近似値0.3の真の値よりも大きくなり、そのため近似値0.3の真の値とも一致しない。
この問題は0.30000000000000004.comというWebサイトが作られるほどよく知られている話である。
JavaScriptのみならず、浮動小数点演算の存在する全ての言語で発生する現象だ。
Patching numbers
NumberやStringなどのラッパーオブジェクトに勝手にメソッドを追加できる。
Number.prototype.isOne = function () {
return Number(this) === 1
}
1.0.isOne() // -> true
1..isOne() // -> true
2.0.isOne() // -> false
(7).isOne() // -> false
他のオブジェクトと同じように拡張することができるが、到底勧められない。
Comparison of three numbers
1 < 2 < 3 // -> true
3 > 2 > 1 // -> false
そもそも3数の評価は想定しているだろう動作をしていない。
1 < 2 < 3 // 先に1<2が評価
true < 3 // trueは数値型にすると1
1 < 3 // -> true
3 > 2 > 1 // 先に3>2が評価
true > 1 // trueは数値型にすると1
1 > 1 // -> false
>
を>=
にすることで、この場合は動くようになる。
3 > 2 >= 1 // true
関係演算子の詳細については以下を参照。
12.10 Relational Operators
Funny math
JavaScriptでの演算は、想定してなかった結果になることがしばしばある。
3 - 1 // -> 2
3 + 1 // -> 4
'3' - 1 // -> 2
'3' + 1 // -> '31'
'' + '' // -> ''
[] + [] // -> ''
{} + [] // -> 0
[] + {} // -> '[object Object]'
{} + {} // -> '[object Object][object Object]'
'222' - -'111' // -> 333
[4] * [4] // -> 16
[] * [] // -> 0
[4, 4] * [4, 4] // NaN
最初の4例はどうなっているのか。
加算演算子でどうなるかの一覧は次のとおりだ。
Number + Number -> 加算
Boolean + Number -> 加算
Boolean + Boolean -> 加算
Number + String -> 文字列連結
String + Boolean -> 文字列連結
String + String -> 文字列連結
それ以外の例についてはどうか。
[]
と{}
には、加算の前に暗黙的にToPrimitiveとToStringメソッドが呼び出されているのだ。
仕様の詳細については以下を参照。
12.8.3 The Addition Operator (+)
7.1.1 ToPrimitive(input [,PreferredType])
7.1.12 ToString(argument)
Addition of RegExps
正規表現リテラルにアクセスできることを知っているか?
// toStringを上書き
RegExp.prototype.toString = function() {
return this.source
}
/7/ - /5/ // -> 2
21.2.5.10 get RegExp.prototype.source
Strings aren't instances of String
文字列は文字列型ではない。
'str' // -> 'str'
typeof 'str' // -> 'string'
'str' instanceof String // -> false
Stringコンストラクタはstringを返す。
typeof String('str') // -> 'string'
String('str') // -> 'str'
String('str') == 'str' // -> true
newを付けると結果が変わる。
new String('str') == 'str' // -> true
typeof new String('str') // -> 'object'
newのついたStringはObjectだ。
new String('str') // -> [String: 'str']
Stringコンストラクタの仕様は以下に記載されている。
21.1.1 The String Constructor
Calling functions with backticks
引数をそのまま返す関数を実装する。
function f(...args) {
return args
}
以下の結果になることは想像に難くない。
f(1, 2, 3) // -> [ 1, 2, 3 ]
だが、関数をバッククォートで呼び出せることは知っているか?
f`true is ${true}, false is ${false}, array is ${[1,2,3]}`
// -> [ [ 'true is ', ', false is ', ', array is ', '' ],
// -> true,
// -> false,
// -> [ 1, 2, 3 ] ]
これはタグ付きテンプレートリテラルと呼ばれる機能で、バッククォート内に書かれたテキストがタグ関数fに渡されることになる。
第一引数には文字列を${}
で分割した配列が入ってきて、第二引数以降は${}
の式の値が順に入ってくる。
function template(strings, ...keys) {
// do something with strings and keys…
}
JavaScriptの中にCSSを書くstyled-componentsライブラリは、Reactコミュニティで人気である。
Call call call
console.log.call.call.call.call.call.apply(a => a, [1, 2])
警告。
訓練を受けていない方が真似すると動悸・息切れ・眩暈等の症状が現れたり、最悪の場合死に至ることもあります。
19.2.3.3 Function.prototype.call(thisArg, ...args)
**19.2.3.1 ** Function.prototype.apply(thisArg, argArray)
A constructor property
const c = 'constructor'
c[c][c]('console.log("WTF?")')() // > WTF?
順に見ていこう。
// 'constructor'という文字列を定義する
const c = 'constructor'
// cはstring型
c // -> 'constructor'
// stringのコンストラクタ
c[c] // -> [Function: String]
// コンストラクタのコンストラクタ
c[c][c] // -> [Function: Function]
// Functionコンストラクタに引数を渡す
c[c][c]('console.log("WTF?")') // -> [Function: anonymous]
// 即時関数で呼ぶ
c[c][c]('console.log("WTF?")')() // > WTF?
Object.prototype.constructor
は作成したインスタンスへの参照を返す。
文字列であればString、数値であればNumberといった具合だ。
Object.prototype.constructor at MDN
19.1.3.1 Object.prototype.constructor
Object as a key of object's property
{ [{}]: {} } // -> { '[object Object]': {} }
これは計算されたプロパティ名という記法だ。
[]
にObjectを渡すと文字列に変換され、'[object Object]'というキー名になる。
({[{}]:{[{}]:{}}})[{}][{}] // -> {}
// structure:
// {
// '[object Object]': {
// '[object Object]': {}
// }
// }
オブジェクトリテラルについては以下を参照。
Object initializer at MDN
12.2.6 Object Initializer
Accessing prototypes with __proto__
知ってのとおり、プリミティブ型にプロトタイプはない。
だが__proto__でプロトタイプを取得できる。
(1).__proto__.__proto__.__proto__ // -> null
プリミティブ型のプロトタイプを呼ぼうとすると、ToObjectメソッドが自動的に呼ばれてラッパーオブジェクトになる。
(1).__proto__ // -> [Number: 0]
(1).__proto__.__proto__ // -> {}
(1).__proto__.__proto__.__proto__ // -> null
__proto__の詳細については以下を参照。
B.2.2.1 Object.prototype.proto
7.1.13 ToObject(argument)
${{Object}}
以下の答えは?
`${{Object}}`
こうなる。
// -> '[object Object]'
まずはObjectをショートハンド{Object}
で定義する。
次にテンプレートリテラルに突っ込むことでtoStringメソッドが呼ばれ、文字列'[object Object]'になる。
12.2.9 Template Literals
Object initializer at MDN
Destructuring with default values
yはいくつになるか。
let x, { x: y = 1 } = { x }; y;
答えは1。
let x, { x: y = 1 } = { x }; y;
// ↑ ↑ ↑ ↑
// 1 3 2 4
1:xを定義。値は未定義。
2:xをオブジェクトのプロパティにする。
3:分割代入でプロパティxの値を取り出してyに代入する。未定義なのでデフォルト値1が使われる。
4:yの値を返す。
Object initializer at MDN
Dots and spreading
[...[...'...']].length // -> 3
何故3か。
スプレッド演算子を使うと@@iterator
メソッドが呼び出され、ループに使うイテレータが返される。
文字列のイテレータは、文字列を1文字ずつの配列にする。
文字列'...'は3文字であり、従って答えは3になる。
[...'...'] // -> [ '.', '.', '.' ]
[...[...'...']] // -> [ '.', '.', '.' ]
[...[...'...']].length // -> 3
もっと長くしても結果は同じになる。
[...'...'] // -> [ '.', '.', '.' ]
[...[...'...']] // -> [ '.', '.', '.' ]
[...[...[...'...']]] // -> [ '.', '.', '.' ]
[...[...[...[...'...']]]] // -> [ '.', '.', '.' ]
Labels
JavaScriptのラベルはあまり知られていないが、多言語とは異なる特徴を持っている。
foo: {
console.log('first');
break foo;
console.log('second');
}
// > first
// -> undefined
ラベル付きステートメントはbreakおよびcontinueと共に使う。
ラベルを用いて範囲を指定し、breakで実行を中断するか、continueで次のループに進むかを選択できる。
上記例ではconsole.log('first');
の後でbreakして実行を中断している。
13.13 Labelled Statements
Labeled statements at MDN
Nested labels
a: b: c: d: e: f: g: 1, 2, 3, 4, 5; // -> 5
ラベルはネストできる。
12.16 Comma Operator (,)
Insidious try..catch
(() => {
try {
return 2;
} finally {
return 3;
}
})()
答えは3。
13.15 The try Statement
Is this multiple inheritance?
これは多重継承ではないか?
new (class F extends (String, Array) { }) // -> F []
いいえ。
注目する点は(String, Array)
だ。
グループ化演算子は最後の値を返すので、(String, Array)
の値はArray
になる。
従って、このクラスは単にArray
を継承しているだけだ。
14.5 Class Definitions
12.16 Comma Operator (,)
A generator which yields itself
自分自身を返すジェネレータを考えてみる。
(function* f() { yield f })().next()
// -> { value: [GeneratorFunction: f], done: false }
見てのとおり、返ってくるのは値fを持つオブジェクトだ。
つまり、こうなる。
(function* f() { yield f })().next().value().next()
// -> { value: [GeneratorFunction: f], done: false }
// and again
(function* f() { yield f })().next().value().next().value().next()
// -> { value: [GeneratorFunction: f], done: false }
// and again
(function* f() { yield f })().next().value().next().value().next().value().next()
// -> { value: [GeneratorFunction: f], done: false }
このようになる理由は以下を参照。
25 Control Abstraction Objects
25.3 Generator Objects
A class of class
(typeof (new (class { class () {} }))) // -> 'object'
クラス内でクラスを宣言しており、エラーになるはずなのに文字列'object'が返ってくる。
ES5以降、キーワードもプロパティ名として使用することができる。
従って以下の文は有効だ。
const foo = {
class: function() {}
};
さて、ES6においてメソッドの短縮構文が導入された。
つまり上記はこう書ける。
class {
class() {}
}
これはただのクラスであり、従ってその型は'object'になる。
14.3 Method Definitions
14.5 Class Definitions
Non-coercible objects
well-known symbolsの型強制を回避する。
function nonCoercible(val) {
if (val == null) {
throw TypeError('nonCoercible should not be called with null or undefined')
}
const res = Object(val)
res[Symbol.toPrimitive] = () => {
throw TypeError('Trying to coerce non-coercible object')
}
return res
}
こうやって使える。
// objects
const foo = nonCoercible({foo: 'foo'})
foo * 10 // -> TypeError: Trying to coerce non-coercible object
foo + 'evil' // -> TypeError: Trying to coerce non-coercible object
// strings
const bar = nonCoercible('bar')
bar + '1' // -> TypeError: Trying to coerce non-coercible object
bar.toString() + 1 // -> bar1
bar === 'bar' // -> false
bar.toString() === 'bar' // -> true
bar == 'bar' // -> TypeError: Trying to coerce non-coercible object
// numbers
const baz = nonCoercible(1)
baz == 1 // -> TypeError: Trying to coerce non-coercible object
baz === 1 // -> false
baz.valueOf() === 1 // -> true
A gist by Sergey Rubanov
6.1.5.1 Well-Known Symbols
Tricky arrow functions
アロー関数がある。
let f = () => 10
f() // -> 10
だが下の例は思った通りに動かない。
let f = () => {}
f() // -> undefined
{}
を期待したのにundefined
が返ってきた。
中括弧はアロー関数の構文の一部と見做されるため、関数の本体がなくなる。
arguments and arrow functions
引数のある関数だ。
let f = function(){
return arguments;
}
f('a'); // -> { '0': 'a' }
アロー関数にしよう。
let f = () => arguments;
f('a'); // -> Uncaught ReferenceError: arguments is not defined
アロー関数は文字数が短く、this
をレキシカルに固定できる、これまでのfunction
の短縮構文だ。
ところでアロー関数は引数をarguments
にバインドしない。
かわりにスプレッド演算子で引数を受け取る必要がある。
let f = (...args) => args;
f('a');
Arrow functions at MDN.
Tricky return
区別が付くだろうか。
(function () {
return
{
b : 10
}
})() // -> undefined
(function () {
return {
b : 10
}
})() // -> { b: 10 }
これは自動セミコロン挿入機能によるものである。
改行の後には、大抵セミコロンが自動的に挿入されてしまう。
前者の例ではreturnと{
の間にセミコロンが入り、関数はundefinedを返し、オブジェクトリテラルが評価されることはない。
11.9.1 Rules of Automatic Semicolon Insertion
13.10 The return Statement
Accessing object properties with arrays
var obj = { property: 1 }
var array = ['property']
obj[array] // -> 1
多次元配列にしたらどうなる?
var map = {}
var x = 1
var y = 2
var z = 3
map[[x, y, z]] = true
map[[x + 10, y, z]] = true
map["1,2,3"] // -> true
map["11,2,3"] // -> true
ブラケット記法[]
は、渡された値をtoStringに通す。
配列に要素がひとつしかない場合、その結果は要素だけをtoStringしたのと同じ結果になる。
['property'].toString() // -> 'property'
Null and Relational Operators
null > 0; // false
null == 0; // false
null >= 0; // true
簡単にまとめると、『もしnull<0
がfalse
であれば、null>=0
はtrue
である』
詳しい説明はここにある。
Number.toFixed() display different numbers
Number.toFixedはブラウザによって異なる結果になる。
0.7875.toFixed(3)
// FireFox: -> 0.787
// Chrome: -> 0.787
// IE11: -> 0.788
0.7876.toFixed(3)
// FireFox: -> 0.788
// Chrome: -> 0.788
// IE11: -> 0.788
20.1.3.3 Number.prototype.toFixed (fractionDigits)
Other resources
wtfjs.com
その不規則で矛盾に満ちていて直感的でないWeb言語について。
Wat
Gary Bernhardtが2012年のCodeMashで発表したライトニングトーク。
What the... JavaScript?
Kyle SimpsonsによるJavaScriptの素敵な仕様についてのトーク。
彼は、クリーンでエレガントで可読性の高いコードを作成し、オープンソースコミュニティに貢献できるよう人々を導きたいと考えている。
License
WTFPL 2.0ライセンスだ。
まとめ
知らない仕様や、意味のわからない動作がたくさんあった。