flat(depth)はネストした配列を指定した深さまで平坦化し、flatMap(callback)は各要素を変換したあと1段だけ平坦化します。どちらも元配列を変更せず、新しい配列を返します。2段以上を解除するならflat()、1要素から0〜複数要素を作るならflatMap()が基本です。
この記事は「ネスト解除」の個別メソッドを扱います。配列変換全体の選び方はmap・filter・reduceの親記事、配列作成はfill・Array.fromに分けています。
flat()とflatMap()はどう違う?
| API | 処理 | 平坦化する深さ | 元配列 |
|---|---|---|---|
flat(depth) |
既にある入れ子を解除 | 既定は1、指定可能 | 変更しない |
flatMap(callback) |
mapしてからflat | 1固定 | 変更しない |
map(callback) |
各要素を1つの値へ変換 | 解除しない | 変更しない |
filter(callback) |
条件に合う元要素だけ残す | 解除しない | 変更しない |
flat()でネストを解除する
引数を省略したflat()は1段だけ平坦化します。[1, [2, 3]]を[1, 2, 3]にするだけなら、これで足ります。
const nested = [1, [2, 3], [[4]]];
console.log(nested.flat()); // [1, 2, 3, [4]]
console.log(nested); // 元配列はそのまま
必要な深さが分かるなら数値を指定します。
console.log(nested.flat(2)); // [1, 2, 3, 4]
Infinityで全階層を解除する
flat(Infinity)は見つかる配列の入れ子を再帰的に解除します。入力構造を保つ必要がないと確認した場合だけ使います。
const deeplyNested = [1, [2, [3, [4]]]];
console.log(deeplyNested.flat(Infinity)); // [1, 2, 3, 4]
階層自体に意味があるcategory treeやcomment threadに無条件に使うと、親子関係を失います。必要なdepthを決めるか、treeとして走査します。
flatMap()で変換と1段の平坦化をまとめる
flatMap()は概ねmap().flat(1)に相当します。callbackが配列を返した場合に、その配列だけを1段展開します。map().flat()と違って中間配列を作らず1回の走査で済むため、「変換してから平坦化する」と決まっている処理ではこちらを選びます。flat()とflatMap()はどちらもES2019で追加され、現行の主要ブラウザとNode.jsで使えます。
const lines = ["red,blue", "green"];
const colors = lines.flatMap((line) => line.split(","));
console.log(colors); // ["red", "blue", "green"]
callbackの引数はelement、index、元配列です。処理中に元配列を変更すると結果を追いにくくなるため、変換だけを返します。
flatMap()で0件・1件・複数件を返す
callbackから空配列を返せばその要素を除外し、1要素配列なら残し、複数要素なら展開できます。この「1入力から0〜複数出力」がflatMapの固有範囲です。
const values = [2, 3, 4];
const result = values.flatMap((value) => {
if (value % 2 !== 0) return [];
return [value, value * 10];
});
console.log(result); // [2, 20, 4, 40]
単に条件に合う要素を残すだけならfilter()のほうが意図が明確です。必ず1対1で変換するならmap()を使います。
flatMap()は2段以上を平坦化できない
flatMap()が解除するのは1段だけです。callbackが2段の配列を返すと、1段は残ります。
const result = [1, 2].flatMap((value) => [[value]]);
console.log(result); // [[1], [2]]
さらに解除する必要があるなら、処理を分けてflat(depth)を使います。ただし、深い構造を作った原因を見直すほうが分かりやすい場合もあります。
配列以外の戻り値は平坦化される?
callbackが配列以外を返した場合、その値はそのまま1要素として追加されます。array-like objectを返しただけでは自動展開されません。
const result = [1, 2].flatMap((value) => ({
0: value,
length: 1,
}));
console.log(result.length); // 2
console.log(result[0]); // objectのまま
空スロットはどう扱われる?
flat()は平坦化対象の階層にある空slotを取り除きます。flatMap()のcallbackも元配列の空slotでは呼ばれません。undefinedが入った要素とは別です。
const sparse = [1, , 3];
console.log(sparse.flat()); // [1, 3]
console.log([1, undefined, 3].flat()); // [1, undefined, 3]
欠損位置を保つデータでは、flat系を使う前にslotの意味を確認してください。
flat()が期待どおりにならないときの確認手順
console.dir()で実際の入れ子の深さを確認する- 配列ではなくobjectやNodeListを返していないか確認する
flatMap()は1段固定であることを確認する- 空slotと
undefinedを区別する - 元配列は変わらないため、戻り値を変数に代入する
選択したメソッドが目的に合わない場合は、map・filter・reduceの役割表に戻ってください。存在確認だけが目的なら、新しい配列を作らないsome・everyが適します。