くそったれJavaScript

  • 280
    Like
  • 0
    Comment

以下は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として定義されているが、これは厳密には数値ではない。

20.1.2.9 Number.MIN_VALUE

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形式コメントについても未だに対応している。
むしろ完全に仕様の一部となっている。

B.1.3 HTML-like Comments

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

他のオブジェクトと同じように拡張することができるが、到底勧められない。

20.1 Number Objects

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コミュニティで人気である。

12.3.7 Tagged Templates

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<0falseであれば、null>=0trueである』
詳しい説明はここにある。

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ライセンスだ。

まとめ

知らない仕様や、意味のわからない動作がたくさんあった。