ブラウザの開発者ツールで以下を確認してみましょう。
a=[10]
a[5]=20
a
a.length
empty × 4
の部分が曲者で、実はこの部分には「要素は無い」。
a[1] を評価すると undefined
になるので「aの1番目には undefined
が入っている」かのように見えますが、正確には存在しないキーを指定されたから undefined
になっただけです。
それが何?
さて、例えば終端が不確定な配列データをグラフ上にプロットしたいとかの要件で「配列の長さを最低N個まで拡張する」という関数を作る場合、何も考えずに実装するとこういうソースになると思います。
function fillUp(arr, length) {
arr[length - 1] = arr[length - 1]; // 配列の長さがlength以上なら何も起こらず、未満ならundefinedが代入される事で配列の長さがlengthまで伸びる、ような気がする
// a.length = Math.max(a.length, length) // こちらでも
}
冒頭のaを与えて fillUp(a, 10)
を実行すると確かに a[9]
に undefined
は入るし a.length
も10になります。
が、lengthが10だからといって10回ループする事を期待して a.forEach()
を実行しても実際には3回しかループしません。
fillUp(a, 10)
b=a.length
a.forEach(() => --b) // bはゼロになるはず
b
更に、「undefinedの要素を別の値に置き換えた配列を作りたい」といった処理も失敗します。
a2 = a.map(v => v === undefined ? 'empty' : v)
a2[1] // 'empty'に…ならない!
今aは具体的にはどういう状態か、以下で確認できます。
[...Array(a.length)].map((x,i) => ({index:i, exists: (i in a), value: a[i]}) )
おわかりいただけただろうか。
紛らわしいので何とかしたい
万能薬は無い気がします。どなたかご存知なら教えてください。
参照する側で気を付けるなら a.~
をやめて [...Array(a.length)].map((x,i) => a[i]).~
を使います。面倒臭い!
a.map(v => v === undefined ? 'empty' : v)
[...Array(a.length)].map((x,i) => a[i]).map(v => v === undefined ? 'empty' : v)
データを作る側で対処するなら、地道に埋めたてます。
function fillUp(arr, length) {
arr[length - 1] = arr[length - 1];
}
function fillUp(arr, length) {
[...Array(length)].forEach((x,i) => arr[i] = arr[i])
}
という事はpopも実際に代入されている値しか取り出さないのでは?
あ、そこはlength準拠の挙動なんですね…。
番外編 - これは添え字?
a.lengthは1から変わらず、 "2.1" というキーの独自プロパティが生えただけでした。 forEach
や map
や pop
も2.1には反応しません。「だからどうした?」とか言わない。