事の発端
長さnで値は0/1ランダムな配列を作ろうとして、以下のようなコードを書きました。
new Array(10).map(function() {
return Math.floor(Math.random() * 2);
});
結果は[1, 0, ...]となるのが期待されますが、残念ながら[undefined × 10]1になります。
ほげー。
new Array(1).hasOwnProperty(0)がfalseを返す
> new Array(1).length
1
> new Array(1).hasOwnProperty(0)
false
> [undefined].length
1
> [undefined].hasOwnProperty(0)
true
なるほど、ここから察するにnew Array(len)はlengthは初期化するけれど、各要素は初期化されない、というよりは確保されていないような動作であることが分かりました。
new Array(2)[1]のように要素アクセスするとundefinedを返しますが、これは要素が返されているのではなく、プロパティがないのでundefinedというわけですね。
ECMA Scriptの仕様より
ググってもあまり情報が出てこないのでECMA Scriptの仕様書を探ってみました。
new Array(len)はこのあたりになります。読む限りではlengthを初期化する話はありますが、要素をどうこうするという記述はありません。
一方で要素で初期化する方を見てみると、
The 0 property of the newly constructed object is set to item0 (if supplied); the 1 property of the newly constructed object is set to item1 (if supplied); (以下略)
と書かれています。こちらに明示的に要素を初期化する記述があることを考えれば、そういったことが書かれていないnew Array(len)は要素は初期化しないと考えるのが正しそうです。
続いてArray.prototype.mapを見てみると、
6. Let A be a new array created as if by the expression new Array(len) where Array is the standard built-in constructor with that name and len is the value of len.
7. Let k be 0.
8. Repeat, while k < len
a. Let Pk be ToString(k).
b. Let kPresent be the result of calling the [[HasProperty]] internal method of O with argument Pk.
c. If kPresent is true, then
...
9. Return A.
と記述されています。[[HasProperty]]はhasOwnPropertyと考えれば良さそうで、そうするとnew Array(len).hasOwnProperty(Pk)はfalseなので、ループは回れど各要素には触れられない(関数も呼ばれない)ということが分かります。
納得です。
発端のプログラムをシンプルに書くには?
- Create an array with same element repeated multiple times in javascript
- Most efficient way to create a zero filled JavaScript array?
あたりを参照。
for文
var a = [];
for (var i = 0; i < 10; i++) {
a.push(Math.floor(Math.random() * 2));
}
そりゃそうなのですが、そもそもforを書きたくないのが発端なので...。
Array.apply(null, Array(10))
Array.apply(null, Array(10)).map(...);
newは省略できるという話があるようです。
15.4.1 The Array Constructor Called as a Function
When Array is called as a function rather than as a constructor, it creates and initialises a new Array object. Thus the function call Array(…) is equivalent to the object creation expression new Array(…) with the same arguments.
Array.apply(null, Array(10))は要するにArray(undefined, undefined, ...)なので、これならundefinedで初期化された10個の要素をもつ配列が生成されるというわけですね。
なるほど。
Array.prototype.fill
Array(10).fill(0).map(...);
実はES6でArray.prototype.fillというのが追加されていたようです。
Chrome 45には入っているようで、確かに動作しました。手元にあるio.js v2.3.4ではダメでした。
使えるならこれが一番良さそうですね。
Array(10).join(0).split('')
Array(10).join(1).split('').map(...)
せやなぁ...
シンプル明快ですが、文字列にしてsplitしているので効率はよろしくなさそうですね。
underscoreやlodashが使える場合
色々出来そうです。
_.range(10).map(function() { return _.random(1) });
_.range(1, 11, 0).map(_.random);
_.times(10, _.constant(1)).map(_.random);
_.fill(Array(10), 1).map(_.random);
便利ですね。
-
ChromeのDevToolの場合。Nodeの場合。
[ , , , , , , , , , ]。 ↩