Menu
日本語

JavaScript高階関数入門 - map/filter/reduceの使い方

JavaScriptの高階関数をゼロから解説。関数を引数として渡す・戻り値として返すという考え方と、実務で毎日使うmap・filter・reduceの基本パターンをまとめました。

関数は「値」である

JavaScriptでは、関数も数値や文字列と同じように、ひとつの値として扱えます。変数に入れたり、配列に詰めたり、他の関数に引数として渡したり、関数から返したりできるわけです。この性質を押さえておくだけで、プログラミングの幅がぐっと広がります。

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

関数を値として扱うことに慣れてくると、高階関数もそれほど難しく感じなくなります。高階関数とは、関数を引数として受け取る、関数を返す、あるいはその両方を行う関数のことです。定義としてはそれだけ。

関数を引数として渡す

一番よく見るパターンは、コールバック関数を受け取って代わりに実行してくれる関数です。実は意識しないうちに、すでに使っているはずです。

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

forEach は高階関数の代表例で、渡した関数を要素ごとに 1 回ずつ呼び出してくれます。setTimeout も同じく高階関数で、こちらは指定した時間が経ってから関数を実行します。開発者は 何をするか だけを書き、いつ 何回 実行するかはこれらの関数に任せればいい、というわけです。

自分で高階関数を書くときも考え方はまったく同じです。次のコードは、条件が true のときだけコールバックを実行する小さな関数の例です:

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

action は、たまたま関数を値に持っているだけのただのパラメータです。action() と呼び出せば、渡された中身がそのまま実行されます。

実際によく使う3つ: map・filter・reduce

配列には高階メソッドが用意されていて、普段書いている for ループのほとんどを置き換えられます。この3つを覚えるだけで、日常のコードがぐっと短く、読みやすくなります。

map — すべての要素を変換する

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

map は配列の要素ごとに関数を 1 回ずつ呼び出し、その戻り値を集めて新しい配列を作ります。長さは同じで、中身だけが変換されるイメージです。元の配列はそのまま、変更されません。

filter — 条件に合うものだけを残す

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

filter は、コールバックが真値(truthy)を返した要素だけを残して、それ以外は捨てていきます。戻り値は新しい配列で、元より短くなることもあります。

reduce — 配列を1つの値にまとめる

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

reduce は万能選手です。コールバックには「これまでの累積値」と「今の要素」が渡ってきて、return した値が次の累積値になります。第2引数(ここでは 0)は初期値ですね。

これらはチェーンしてつなげられます。このスタイルの真価が発揮されるのはまさにここ。

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

上から下へ素直に読むだけ。支払い済みの注文を絞り込み、価格を取り出し、合計する。ループもなければ、書き換える変数も、オフバイワンの心配もない。

関数を返す関数

高階関数のもう一つの顔がこれ。別の関数を組み立てて返してくれる関数です。

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

multiplyBy(2) を一度呼び出すと、新しい関数が返ってきます。その返された関数は factor の値をちゃんと覚えていて、これがいわゆる クロージャ です。クロージャについては別のページで詳しく扱うので、ここではひとまず「multiplyBy に違う引数を渡すだけで、同じテンプレートから用途別の関数をいくつでも作れる」とだけ押さえておけばOKです。

このパターンは本当にいろんな場面で登場します。

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

定義はひとつ、再利用できる関数はふたつ。warninfo を別々に手書きして、あとから同期を取る手間を考えたら、こっちのほうが断然ラクです。

名前付き関数とインラインのコールバック関数

コールバック関数は、その場でアロー関数を書いて渡してもいいし、名前で呼び出してもOKです。どちらも動くので、読みやすいほうを選べば問題ありません。

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

isEvenを(カッコなしで)渡すと、関数そのものを引き渡すことになります。()を付けてしまうとその場で呼び出されて、戻り値 が渡されてしまう——初心者がやりがちなミスです。

nums.filter(isEven);     // 正しい: 関数を渡している
nums.filter(isEven());   // 誤り: 引数なしで isEven を呼び出し、その結果を渡している

コールバックが数行を超えて長くなりそうなら、思い切って外に出して名前を付けましょう。だいたいの場合、呼び出し側のコードも読みやすくなります。

実際に書いてみる

高階関数の真価は、小さな関数を組み合わせて使うときに発揮されます。たとえば、商品のリストから「手頃な価格」かつ「在庫あり」のものだけを抜き出し、名前を大文字にしたい、というケースを考えてみましょう。

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

ヘルパー関数はそれぞれ一つの役割だけを持ち、配列メソッドもそれぞれ一つの変換だけを担当します。こうしてできたパイプラインは、「どうループを回すか」ではなく「何をしたいか」の仕様書のように読めるわけです。

高階関数を使うべきでない場面

高階関数は便利ですが、どんなループでも置き換えられる万能薬ではありません。

  • 途中で処理を打ち切りたい場合は、forEach から無理やり抜け出すより、forfor...ofbreak を組み合わせるほうがスッキリ書けます。
  • コールバックの中で非同期処理をしたい場合、mapforEachawait してくれません。for...ofawait を組み合わせるか、mapPromise.all をセットで使いましょう。
  • コールバックが共有状態を書き換えているなら、そもそもこのスタイルの良さから外れています。普通のループに戻すか、新しい値を返す形にリファクタリングしましょう。

ハマる場面で使えば、mapfilterreduce は日々のコードからループの定型文をごっそり削ってくれます。ただ、何でもかんでも高階関数で書こうとすると、逆に読みにくくなってしまいます。意図が一番はっきり伝わる道具を選びましょう。

次回:オブジェクト

積み上げていく価値がある値は、関数だけではありません。関連するデータとふるまいをまとめるための JavaScript の主役といえばオブジェクトです。さっきまで filtermap で扱っていた配列の中身も、ほとんどがオブジェクトだったはずです。次のページではそのオブジェクトを見ていきます。

よくある質問

JavaScriptの高階関数とは何ですか?

高階関数とは、「関数を引数として受け取る」「関数を戻り値として返す」のうち、少なくとも片方を満たす関数のことです。Array.prototype.mapsetTimeoutaddEventListenerなどはいずれも高階関数で、渡したコールバック関数を内部で呼び出してくれます。

map・filter・reduceの違いは?

mapは各要素を変換して、元と同じ長さの新しい配列を返します。filterはコールバックが真を返した要素だけを残すので、結果の配列は元より短くなることがあります。reduceは配列を1つの値に畳み込む関数で、要素を順番に組み合わせて最終結果を作ります。いずれもコールバックを受け取る高階関数です。

関数から関数を返すのはどんな場面で役立ちますか?

同じロジックを繰り返さずに、設定違いの小さなヘルパー関数を量産したいときに便利です。たとえばmultiplyBy(n)という関数はn倍する新しい関数を返すので、multiplyBy(2)multiplyBy(10)のように呼び出すだけで専用関数が手に入ります。これはクロージャを活用したパターンで、イベントハンドラやミドルウェア、ユーティリティライブラリでもよく使われます。

Coddyでコードを学ぼう

始める