Edited at

JavaScriptのSpreadOperatorはhasOwnPropertyを見ていない?

More than 1 year has passed since last update.

何かおかしいところあったら教えてください。


ことの発端

テスト用のデータとして、['test1', 'test2', 'test3']のように、100要素くらいある配列をサクッと生成してみたかった。

そして最終的に行き着いたのが次のコードだった。

[...(Array(10))].map((_, i) => `test${i}`);

その後調べてみると、より正しそうな方法を書いたブログを見つけたので、実際に使うときはここを参考にすると良さそう。

JavaScriptで0からn-1までを要素にもつArrayを作成する方法

さて、上記のブログで目的は達成されたが、何故自分が書いたコードが動いていたのだろう。色々と試してみた。

動作確認環境:Chrome 55.0


JavaScriptの配列の不思議

これは割と知っている人も多いことだと思うけど、Array(10)[10]は違う。前者は「サイズが10の配列を作成」で、後者は「10という要素を持つ配列を作成」だ。

これを利用して、「10回ループを回すだけ」の目的で配列を作ろうとした。そのときのコードが以下。

Array(10).forEach(e => console.log('hoge'));

ただし、これは期待通りに動かない。原因は、Array()lengthプロパティは持つが、配列の各要素は持たない配列を生成するためだ。

よくわからないことを言っている気がするけど、次のサンプルを見ると明確だ。

Array(10).length;

// => 10

Array(10).hasOwnProperty(0);
// => false

[10].hasOwnProperty(0);
// => true

ここからわかる事は、ArrayのmapforEachなどの関数は、lengthプロパティだけでなくhasOwnProperty(又はそれに類するもの)を元にループを回すか判定しているということだ。

そんなこんなでArray(10).forEach()というコードは動かないことがわかった。

そこで、(これは完全に思いつきで)ES2016のSpread Operatorを使ってみたらどうだろうと、試してみた。Spread Operatorは、オブジェクトや配列を展開してくれる式だ。

例えば、次のように使える。

const aray1 = [1, 2, 3];

const array2 = [...array1, 4, 5];

console.log(array2);
// => [1, 2, 3, 4, 5]

今回のケースだと、こういう形になる。

[...(Array(10))].forEach(e => console.log('hoge'));

すると、これは期待通りに動き、hogeが10回出力される結果となった。Spread Operatorにより、lengthのみだった配列が、要素を持った通常の配列に変換された。

Array(10)

// => [undefined × 10]

[...(Array(10))]
// => [undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined]

つまりSpread Operatorは、配列が実際に要素を持っているかどうかによらず、lengthの情報から配列の展開を行っているらしい。Array()で初期化した配列は要素を持たないので、undefinedを10個持つ配列に変換されたようだ。

ちなみにArray.from()でもSpreadOperatorと同様の結果になった。

Array.from(Array(10))

// => [undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined]


結論

mapforEachhasOwnProperty相当の評価を行ってループを回しているが、Spread Operatorではその判定は無い。

さて、これが何の役に立つのか...