8
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

JavaScriptの中身があるけど中身のない配列について

Last updated at Posted at 2022-06-25

空じゃないけど空の配列の話。(配列とかおれおれ 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.hasOwnPropertyin演算子、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

8
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?