空じゃないけど空の配列の話。(配列とかおれおれ Advent Calendar2018 – 22 日目) | Ginpen.com を読んで面白かったので、JavaScript の空じゃないけど空な配列について自分でも調べてみた。
はじめに
JavaScript の配列で以下のように中身があるようで中身のない配列がある。
const arr = [10, 20, 30];
arr[4] = 50;
console.log(arr);
// [ 10, 20, 30, <1 empty item>, 50 ]
MDN では空の配列スロット
と呼んでいそうだったので、この記事内では空の配列スロット
と呼ぶ。
in 演算子は、空の配列スロットに対して false を返します。直接アクセスしても undefined が返されます。
MDN より引用
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/in
どんなときに空の配列スロットが生まれるのか?
Array コンストラクターや配列のサイズを超えたインデックスでの代入、配列初期化子、delete
演算子、length
の書き換えで生まれる。
/* Arrayコンストラクター */
const arr = new Array(3);
console.log(arr);
// [ <3 empty items> ]
/* 配列のサイズを超えたインデックスでの代入 */
const arr = [10, 20, 30];
arr[4] = 50;
console.log(arr);
// [ 10, 20, 30, <1 empty item>, 50 ]
/* 配列初期化子 */
const arr = [10, 20, 30, , 50];
console.log(arr);
// [ 10, 20, 30, <1 empty item>, 50 ]
/* delete演算子 */
const arr = [10, 20, 30];
delete arr[2];
console.log(arr);
// [ 10, 20, <1 empty item> ]
/* lengthプロパティの書き換え */
const arr = [10, 20, 30];
arr.length = 5;
console.log(arr);
// [ 10, 20, 30, <2 empty items> ]
要素にアクセスするとどうなるのか?
undefined
が返ってくる。
const arr = [10, 20, 30];
arr[4] = 50;
console.log(arr);
// [ 10, 20, 30, <1 empty item>, 50 ]
console.log(arr[3]);
// undefined
配列も添字をプロパティ名として持つ Object として考えるとわかりやすい。
const obj = {};
obj[0] = 10;
obj[1] = 20;
obj[2] = 30;
obj[4] = 50;
console.log(obj[3]);
// undefined
意図してundefined
を入れている場合、どうやって区別するのか?
Object.prototype.hasOwnProperty
、in
演算子、Object.keys
などを使うことで空の配列スロットかどうかを判断できる。
また、Array.prototype.keys
では空の配列スロットの区別はつけれない。
Object.key
では Own Property を見てるのに対し、Array.prototype.keys
では見てないのが起因してそう。
/* Object.prototype.hasOwnProperty */
const arr = [10, 20, undefined];
arr[4] = 50;
console.log(arr);
// [ 10, 20, undefined, <1 empty item>, 50 ]
for (let i = 0; i < arr.length; i++) {
console.log(i, arr.hasOwnProperty(i));
}
// 0 true
// 1 true
// 2 true
// 3 false
// 4 true
/* in演算子 */
const arr = [10, 20, undefined];
arr[4] = 50;
console.log(arr);
// [ 10, 20, undefined, <1 empty item>, 50 ]
for (let i = 0; i < arr.length; i++) {
console.log(i, i in arr);
}
// 0 true
// 1 true
// 2 true
// 3 false
// 4 true
/* Object.keys */
const arr = [10, 20, undefined];
arr[4] = 50;
console.log(arr);
// [ 10, 20, undefined, <1 empty item>, 50 ]
console.log(Object.keys(arr));
// [ '0', '1', '2', '4' ]
/* Array.prototype.keys */
const arr = [10, 20, undefined];
arr[4] = 50;
console.log(arr);
// [ 10, 20, undefined, <1 empty item>, 50 ]
console.log(Array.from(arr.keys()));
// [ 0, 1, 2, 3, 4 ]
forEach などのループ処理はどうなるか?
Array.prototype.forEach
は Own Property を見てるため、空の配列スロットでコールバックが呼ばれない。
/* Array.prototype.forEach */
const arr = [10, 20, 30];
arr[4] = 50;
console.log(arr);
// [ 10, 20, 30, <1 empty item>, 50 ]
arr.forEach((val, idx) => {
console.log(val, idx);
});
// 10 0
// 20 1
// 30 2
// 50 4
Array.prototype.map
も同様にコールバックは呼ばれないが、返り値の配列の length は同じままで、empty item が含まれている。
/* Array.prototype.map */
const arr = [10, 20, 30];
arr[4] = 50;
console.log(arr);
// [ 10, 20, 30, <1 empty item>, 50 ]
const mappedArr = arr.map((val, idx) => {
console.log(val, idx);
return val * idx;
});
// 10 0
// 20 1
// 30 2
// 50 4
console.log(mappedArr);
// [ 0, 20, 60, <1 empty item>, 200 ]
Array.prototype.filter
では、返り値の配列はイメージしていたものと一致する。
(コールバック自体が呼ばれないため、true
判定にならない?)
/* Array.prototype.filter */
const arr = [10, 20, 30];
arr[4] = 50;
console.log(arr);
// [ 10, 20, 30, <1 empty item>, 50 ]
const filteredArr = arr.filter((val, idx) => {
console.log(val, idx);
return true;
});
// 10 0
// 20 1
// 30 2
// 50 4
console.log(filteredArr);
// [ 10, 20, 30, 50 ]
Array.prototype.find
に関しては、Own Property を見てないので、空の配列スロットもコールバックが呼ばれる。
/* Array.prototype.find */
const arr = [10, 20, 30];
arr[4] = 50;
console.log(arr);
// [ 10, 20, 30, <1 empty item>, 50 ]
const found = arr.find((val, idx) => {
console.log(val, idx);
});
// 10 0
// 20 1
// 30 2
// undefined 3
// 50 4
for...of
は空の配列スロットも含まれる。(わからない)
/* for...of */
const arr = [10, 20, 30];
arr[4] = 50;
console.log(arr);
// [ 10, 20, 30, <1 empty item>, 50 ]
for (let val of arr) {
console.log(val);
}
// 10
// 20
// 30
// undefined
// 50
さいごに
普段、当たり前に使っている配列もよくよく調べてみると面白いですね。
実際に役に立つタイミングは少ないかもしれませんが、面白いと思っていただければ幸いです。
Refs
- 空じゃないけど空の配列の話。(配列とかおれおれ Advent Calendar2018 – 22 日目) | Ginpen.com
- JavaScript の疎な配列 - 配列 (Array) - JavaScript の基本 - JavaScript 入門
- MDN
- Array - JavaScript | MDN
- length と数値プロパティとの関係 - Array - JavaScript | MDN
- 配列の要素の削除 - delete 演算子 - JavaScript | MDN
- Object.prototype.hasOwnProperty() - JavaScript | MDN
- in 演算子 - JavaScript | MDN
- Object.keys() - JavaScript | MDN
- Array.prototype.keys() - JavaScript | MDN
- Array.prototype.forEach() - JavaScript | MDN
- for...of - JavaScript | MDN
- ECMAScript® 2023 Language Specification
- Object.keys - ECMAScript® 2023 Language Specification
- Array.prototype.keys - ECMAScript® 2023 Language Specification
- Array.prototype.forEach - ECMAScript® 2023 Language Specification
- Array.prototype.map - ECMAScript® 2023 Language Specification
- Array.prototype.filter - ECMAScript® 2023 Language Specification
- Array.prototype.find ECMAScript® 2023 Language Specification