経緯
JavaScriptの配列にオブジェクトをもたせて、なおかつそれぞれ己の親配列内でのインデックスがあると実装が楽なんで要素として持たせようという場面。
次のようにfill
を使用してデフォルト値p=0
を与えつつ、map
のcallbackの第2引数のindexをq
にセットしようとした。
が、すべてのq
が2になってしまった。
x = Array(3).fill({p:0}).map((v,i)=>{v.q=i;return v;})
// 結果
[
{ p:0, q=2 },
{ p:0, q=2 },
{ p:0, q=2 }
]
結論
fill
関数に渡されたオブジェクトは複製されない。
解説
まあ結論読めばあとは蛇足なんですが、
fill関数は渡された値を各要素値として直接代入するような操作をする。
渡されているのがObjectへの参照である場合、そのひとつの参照を配列の全要素にブチこむため、
せっかく配列なのに見ているObjectは一個という悲しい状態になる。
よって、mapでv.q=i;
を呼ばれるたびに、同じObjectのq
を更新していた。
v.q=0; // x[0].q = 0; x[1].q = 0; x[2].q = 0;
v.q=1; // x[0].q = 1; x[1].q = 1; x[2].q = 1;
v.q=2; // x[0].q = 2; x[1].q = 2; x[2].q = 2;
回避策
僕がArray(3).fill()
を呼ぶのは、
もともとは配列要素の値がempty
のような状態1だとmapが動かないからであって、
デフォルト値を渡すためではなかった。
おとなしくundefined
で埋めてやれば return
とかも書かなくて済む
x = Array(3).fill().map((v,i)=>({p:0,q:i}))
まあ、デフォルト値を明示的に分離したいお気持ち表明をしてしまったせいで躓いたというクソつまんない原因でしたとさ。
-
これもちと謎々なのだが、
Array(3).map((v,i)=>i)
は[0,1]
になってくれない。Array(3)
はコンソールでは[empty x 3]
と表記され、このempty
に該当する要素は処理から除外される。MDNのページには、「配列の中で存在しない要素(設定されたことがない添字・削除された要素・値を割り当てられたことがない要素)に対しては呼び出しません。」と記載がある。正直意味がわからんし処理しろと思うが、この仕様が活きている場所もあるかもしれないので仕方がない。該当するかどうかはObject.keys(Array(2)) // result: []
のようにするとテストできるはずだ。 ↩