undefined 要素とは何か
配列の「undefined 要素」は2種類あります。ここが重要です:値が undefined で“入っている要素”と、インデックスはあるのに“実際には値がない空スロット(疎配列)”です。両者は見た目が似ていて混同しやすいですが、メソッドの挙動が変わるため区別が必須です。
値としての undefined と空スロット(疎配列)の違い
値としての undefined(定義済みの空値)
const a = [undefined, 1];
console.log(a.length); // 2(2要素ある)
console.log(a[0]); // undefined(“値”としての undefined)
JavaScriptこの場合、0番目には“要素が存在し、その値が undefined”です。
空スロット(要素自体が存在しない)
const b = [];
b.length = 2; // 長さだけ伸ばす(値は入れない)
console.log(b); // [ <2 empty items> ] と表示されることが多い
console.log(b[0]); // undefined(読み出すと undefined に見えるが“無い”)
JavaScriptここが重要です:b[0] は読み出せば undefined に見えますが“値がないスロット”。メソッドの通過・スキップ挙動に差が出ます。
実用的な見分け方と生成パターン
in 演算子でスロットの存在を確認
const a = [undefined]; // 値として undefined
const b = [];
b.length = 1; // 空スロット
console.log(0 in a); // true(要素はある)
console.log(0 in b); // false(スロット自体が無い)
JavaScriptここが重要です:in は「そのインデックスに要素が“存在するか”」を判定します。値が undefined かどうかではなく“スロットの有無”です。
疎配列はこうして生まれる
const sparse = Array(3); // [ <3 empty items> ]
// または
const s = [];
s[5] = "X"; // 0〜4 は空スロット
JavaScript疎配列は、length だけ伸ばす、飛びインデックスに代入する、delete で要素を消す(splice ではなく)などで発生します。
メソッドごとの挙動差(ここが最重要)
forEach/map/filter は空スロットを“スキップ”する
const a = [undefined, 1];
const b = Array(2); // [ <2 empty items> ]
a.forEach((v, i) => console.log("A", i, v));
// A 0 undefined
// A 1 1
b.forEach((v, i) => console.log("B", i, v));
// 何も出力されない(空スロットはコールされない)
JavaScriptここが重要です:値として undefined がある場合はコールバックが呼ばれます。空スロットは“存在しない”扱いでスキップされます。
map は長さを保つが、空スロットはそのまま空のまま
const b = Array(2);
const mapped = b.map(x => 0);
console.log(mapped); // [ <2 empty items> ](0 にはならない)
JavaScriptここが重要です:空スロットはコールされないため、結果も“空のまま”。穴が埋まりません。
reduce は空スロットをスキップするが、初期値がないとエラーになる場合あり
const b = Array(2);
b.reduce((acc, x) => acc + (x ?? 0)); // TypeError(初期値が必要)
const sum = b.reduce((acc, x) => acc + (x ?? 0), 0); // 0(安全)
JavaScriptここが重要です:空スロットをスキップするため、初期値がない reduce は“最初の要素”が見つからず失敗します。必ず初期値を置きましょう。
for…of と at は空スロットを“undefined として”扱う
const b = Array(2);
for (const v of b) console.log(v); // undefined, undefined(反復では undefined として出る)
console.log(b.at(0)); // undefined
JavaScriptここが重要です:反復はスロットを辿る性質があり、undefined として現れます。forEach などの“スキップ”系と挙動が違う点に注意。
実務での安全な扱い方(穴を埋める・排除する)
疎配列を“通常の配列”へ正規化
const sparse = Array(3);
const normalized = Array.from(sparse, x => x ?? null); // [null, null, null]
JavaScriptここが重要です:Array.from は空スロットにもマッピングを適用し、“存在する要素”として埋めます。null などに統一すると後工程が安定します。
undefined を安全に処理(デフォルト値を与える)
const xs = [undefined, 1, undefined];
const filled = xs.map(x => x ?? 0); // [0, 1, 0]
JavaScriptここが重要です:null 合体演算子(??)で“null/undefined”にだけデフォルトを適用できます。0 や “” を誤って上書きしません。
欠損を取り除く(非破壊)
const xs = [undefined, 1, null, 2];
const compact = xs.filter(x => x != null); // [1, 2](null と undefined を除去)
JavaScriptここが重要です:!= null は null と undefined の両方に一致する“安全な短縮”。0 や false は残せます。
生成・更新時の落とし穴と対策
delete を使わない(空スロットが生まれる)
const xs = [1, 2, 3];
delete xs[1]; // [1, <1 empty item>, 3](疎配列化)
xs.splice(1, 1); // [1, 3](安全:疎配列を作らない)
JavaScriptここが重要です:要素削除は splice を使い、疎配列を作らないのが鉄則です。
空配列を作るときは中身も初期化する
Array(3).fill(0); // [0, 0, 0](空スロットを作らない)
Array.from({ length: 3 }, (_, i) => i); // [0, 1, 2]
JavaScriptここが重要です:fill や Array.from で初期化しておくと、スキップ挙動に悩まされません。
比較・判定は“undefined と空スロットの違い”を意識
const arr = Array(1);
if (0 in arr) { /* スロットあり(値は空) */ } else { /* スロットなし */ }
JavaScriptここが重要です:存在チェックは in、値チェックは x === undefined、目的に応じて使い分けます。
パターン別レシピ(よくある要件)
欠損をゼロ埋め
function zeroFill(arr) {
return Array.from(arr, x => x ?? 0);
}
JavaScript欠損を除去して詰める
function compact(arr) {
// 空スロットも undefined も null も除去して詰める
return arr.filter((_, i) => i in arr).filter(x => x != null);
}
JavaScript疎配列を安全に map したい
const sparse = Array(3);
const mapped = Array.from(sparse, (_, i) => i * 2); // [0, 2, 4]
JavaScriptまとめ
undefined 要素の扱いで最も重要なのは「値としての undefined」と「空スロット(疎配列)」の区別です。in 演算子でスロットの有無を確認し、delete ではなく splice を使う、初期化には fill/Array.from を選ぶ。コールバック系メソッドは空スロットを“スキップ”するため、必要なら Array.from で正規化するか、null 合体(??)で安全に埋める。この基本を押さえれば、初心者でも欠損データに強い配列処理が安定して書けます。
