Menu
日本語

JavaScript配列メソッド入門:map・filter・reduce完全ガイド

for文の9割を置き換えられる配列メソッド(map・filter・reduce・find・some・every)の使い方と、元の配列を変更するメソッドとしないメソッドの違いをまとめました。

配列には便利なメソッドが最初から揃っている

JavaScriptの配列には、豊富な組み込みメソッドが用意されています。値を変換したり、条件に合うものだけを取り出したり、合計を出したり──forループで書きがちな処理のほとんどは、専用のメソッドを使えば一行で済み、読みやすく、他のメソッドともきれいにつながります。

まず押さえておきたいのがmapfilterreduceの3つ。この3つと、あといくつか仲間のメソッドを覚えるだけで、ループだらけだったコードがひと目で意味のわかる形に一気に縮まります。

index.js
Output
Click Run to see the output here.

各メソッドはコールバックを受け取って何かを返します。どれも nums を書き換えていない点に注目してください。これは早い段階で体に染み込ませておきたいポイントです。

map の使い方:すべての要素を変換する

map は関数を受け取り、配列の各要素に対してその関数を呼び出し、戻り値を集めて同じ長さの新しい配列を作ります。「入力1つにつき出力1つ」が欲しいときに使うメソッドです。

index.js
Output
Click Run to see the output here.

コールバックの第2引数ではインデックスも受け取れます(arr.map((item, i) => ...))。使わないなら無視してOKです。

よくあるミスは、戻り値の配列が不要な場面で map を使ってしまうこと。各要素をただ出力したいだけ、あるいはDBに保存したいだけなら、forEach か普通のループで十分です。

filter の使い方:条件に合う要素だけを残す

filter は各要素に対して述語関数(true か false を返す関数)を実行し、真を返したものだけを残します。返ってくる新しい配列の長さは、元と同じか短くなります。

index.js
Output
Click Run to see the output here.

mapfilter はチェーンさせると自然につながります。チェーンは前から順に読んでいけば、パイプラインとしてそのまま理解できます。

index.js
Output
Click Run to see the output here.

まず filter で絞り込んでから map にかける。こうすれば map は生き残った要素だけを処理すればよくなります。

reduce の使い方:配列を1つの値に畳み込む

3つの中で最も汎用性が高いのが reduce です。(accumulator, item) => newAccumulator というリデューサ関数と初期値を渡すと、配列を順にたどりながら、各要素とそれまでの累積値をリデューサに渡していき、最終的な累積値を返してくれます。

index.js
Output
Click Run to see the output here.

結果が数値である必要はありません。オブジェクト、別の配列、文字列など、積み上げて作れるものなら何でも OK です。

index.js
Output
Click Run to see the output here.

初期値(第二引数)は必ず渡しましょう。省略すると reduce は最初の要素をアキュムレータの初期値として使ってしまい、空配列でエラーになったり、そもそも意図した動作にならないことがよくあります。

reduce は強力ですが、ロジックが複雑になると途端に読みにくくなります。リデューサーが数行を超えるようなら、素直に for...of ループで書いた方がわかりやすいことが多いです。

forEach:副作用専用、戻り値なし

forEach は、配列を返さない map のようなものです。各要素に対して何か 処理を実行したい ときに使います。たとえばログ出力、API 呼び出し、DOM の更新などですね。新しい配列が必要ないケース向けのメソッドです。

index.js
Output
Click Run to see the output here.

覚えておきたいポイントは2つです。

  • forEachundefined を返します。後ろに .map() をチェーンすることはできません。
  • forEach の途中で break はできません。早期に抜けたいときは for...ofsome / every を使いましょう。

もし arr.forEach(x => results.push(transform(x))) のようなコードを書きそうになったら、それは map の出番です。

find と findIndex:ひとつだけ欲しいとき

find は条件に最初にマッチした要素を返し、見つからなければ undefined を返します。findIndex はそのインデックス(見つからない場合は -1)を返します。

index.js
Output
Click Run to see the output here.

find は最初に一致したところで処理を止めます。filter(...)[0] のような書き方は、残りまで全部走査してから捨てることになるので避けましょう。

some と every:配列を真偽値で判定する

some は、少なくとも1つの要素が条件を満たせば true を返します。一方 every は、すべての要素が条件を満たしたときだけ true になります。

index.js
Output
Click Run to see the output here.

どちらも短絡評価です。some は最初に true になった時点で止まり、every は最初に false が出た時点で止まります。「どれか一つでも該当するか?」「全部該当するか?」を判定したいときにぴったりのメソッドです。

slice と splice の違い:コピーか、切り出しか

名前が似ていて紛らわしいのですが、この2つは動きが全然違います。

slice(start, end) は配列の一部を浅くコピーして返します。元の配列には一切手を加えません。end は含まれない点に注意してください。省略すると末尾までが対象になります。

index.js
Output
Click Run to see the output here.

splice(start, deleteCount, ...items) は元の配列を直接書き換える破壊的メソッドです。start の位置から deleteCount 個の要素を取り除き、必要なら新しい要素を挿入します。戻り値は取り除かれた要素の配列です。

index.js
Output
Click Run to see the output here.

覚え方はこう。slice は安全(コピーを返す)、splice は配列を直接手術する(その場で書き換え)。

破壊的メソッドと非破壊的メソッドの違い

この区別はかなり重要です。共有している配列をうっかり書き換えてしまうバグは、原因を突き止めるのが本当に面倒なタイプのバグの一つです。

破壊的メソッド(元の配列を変更し、戻り値は別のものを返すことが多い):

  • push, pop, shift, unshift
  • splice, sort, reverse
  • fill, copyWithin

非破壊的メソッド(新しい配列や値を返し、元の配列はそのまま):

  • map, filter, slice, concat
  • flat, flatMap
  • find, findIndex, some, every, includes, indexOf
  • reduce, reduceRight

特に気をつけたいのが sortreverse の2つ。見た目は無害そうなのに、こっそり元の配列を書き換えてきます。ソート済みのコピーが欲しいときは、先に slice してから並べ替えましょう。

index.js
Output
Click Run to see the output here.

モダンな JavaScript には、これらの非破壊的メソッドのペアとして toSortedtoReversedtoSplicedwith が用意されています。新しい配列を返すので、元の配列には手を付けません。今どきのランタイムならどれでも動くので、使える環境なら積極的に使いましょう。

index.js
Output
Click Run to see the output here.

flat と flatMap

flat はネストされた配列を 1 階層だけ平坦化します(深さを引数で指定すれば、それ以上もいけます)。flatMapmap の後に 1 階層分の flat をかけたもので、1 つの要素から 0 個以上の結果を生成したいときに便利です。

index.js
Output
Click Run to see the output here.

flatMap は「入力1つに対して出力が複数」というような要素を展開したいときに、わざわざ flat() を挟まずに済むスッキリとした書き方です。

実例で組み合わせてみる

ちょっと現実的な例を見てみましょう。注文リストから、完了済みで $50 を超えるものだけを対象に、合計売上を求めてみます:

index.js
Output
Click Run to see the output here.

3 つのメソッドを組み合わせるだけで、ループまわりの面倒な管理なしにパイプラインが完成します。各ステップが「何をしているか」をそのまま物語ってくれるのが魅力です。2 つの filter は 1 つにまとめてもいいのですが、分けておいた方が読みやすく、デバッグのときにも助かることがあります。

次のテーマ:Map と Set

配列は順番のあるデータを扱うのは得意ですが、キーで高速に値を引きたいときや、重複のない集合がほしいときには少し扱いにくいものです。JavaScript には、まさにその用途のために用意された組み込みのデータ構造 MapSet があります。次のページではこの 2 つを見ていきましょう。

よくある質問

map・filter・reduceの違いは?

mapは各要素を変換して、同じ長さの新しい配列を返します。filterは条件に合う要素だけを残して、(大抵は短くなった)新しい配列を返します。reduceは配列を頭からなめていって、最終的に1つの値にまとめる処理です。合計値、オブジェクト、別の配列など、何にでも畳み込めます。

forEachとmapはどう使い分ける?

forEachは各要素に対して関数を実行するだけで、戻り値はundefinedです。つまり副作用を起こすためのメソッド。一方mapは各要素に関数を適用して、その結果を新しい配列として返します。変換後の配列が欲しいならmap、単に要素ごとに何か処理したいだけならforEach(またはfor...of)を使うのが自然です。

元の配列を変更してしまうメソッドはどれ?

破壊的メソッド(元の配列を書き換えるもの)はpushpopshiftunshiftsplicesortreversefillcopyWithinです。それ以外のmapfiltersliceconcatflatflatMapfindsomeeveryreduceなどは、元の配列には手を付けず新しい値を返します。

sliceとspliceはどっちを使えばいい?

slice(start, end)は配列の一部を浅くコピーして返すだけで、元の配列には触りません。splice(start, deleteCount, ...items)は破壊的で、要素をその場で削除・挿入し、削除した要素を返します。覚え方は「sliceは安全、spliceは切った貼った」です。

Coddyでコードを学ぼう

始める