4.5 高階関数と配列操作

JavaScript では、関数を「データ」として扱うことができるため、関数を他の関数の引数に渡したり、戻り値として受け取ったりすることができます。この性質を利用したのが 「高階関数(こうかいかんすう)」 です。

高階関数

関数を引数として受け取ったり、戻り値として返したりする関数のこと。「処理の枠組み」を提供します。

コールバック関数

高階関数に「部品」として渡される関数のこと。「具体的な処理内容」を担当します。

用語と説明だけでは、非常にイメージがつかみにくいかもしれませんが、実際のソースコードで学習していくと、高階関数とコールバック関数とは何かが、より具体的に見えてくると思います。特に大量の配列に入ったデータを一括で処理する際に、高階関数は非常に便利な機能です。

JavaScript で使用される高階関数には、以下のものがあります。

4.5.1 高階関数とコールバック関数を実行するプログラム

ソースフォルダ:public/ch04

ファイル名:higher-order.js

➢ higher-order.html

前項で作った ch04/arrow.html をコピーし、ch04 にペーストしてください。
その際に、ファイル名を higher-order.html に変更します。
ファイル内の arrow.html や arrow.js の記述も、higher-order.html や higher-order.js に修正します。

➢ higher-order.js

// higher-order.js

const numbers = [1, 2, 3, 4, 5];

// 1. filterメソッド:偶数だけを取り出します
const evens = numbers.filter(num => num % 2 === 0);

// 2. mapメソッド:各要素を2倍にします
const doubled = evens.map(num => num * 2);

alert(`元の配列: ${numbers}
偶数を2倍にした結果: ${doubled}`);

実行結果

解説

このプログラムでは filter と map という 2 つの高階関数を使って、配列の値を順次処理しています。
JavaScript における配列については、第 5 章で詳しく扱うため、ここでは「高階関数」「コールバック関数」に注目してソースコードを見ていきましょう。
まず、3行目で numbers という変数を定義し、初期値として1から5までの連続する値を格納しています。

3: const numbers = [1, 2, 3, 4, 5];

6行目で evens という新しい変数を宣言し、numbers.filter という高階関数を使って、numbers 内の要素から偶数になる(条件が num => num % 2 === 0 に該当する) 値を、新しい evens の中に格納しています。
numbers の中の1から5の要素のうち、条件に該当する2と4が、新しい evens の中に格納されます。

6: const evens = numbers.filter(num => num % 2 === 0);

イメージしやすいように、filterの内部動作を追ってみましょう。
filter は、配列の要素を1つずつ順番にコールバック関数に渡し、コールバック関数が true を返した要素だけを集めて、新しい配列を作ります。
numbers.filter(num => num % 2 === 0) の場合、内部では以下のことが起きています。

この結果、true が返された 2 と 4 だけが集められ、新しい配列 [2, 4] が evens に格納されます。
ここで重要なのは、filterが「枠組み」(=配列を1つずつ取り出してループする仕組み)を提供し、コールバック関数が「判定基準」(=偶数かどうか)を提供しているという役割分担です。これが「高階関数とコールバック関数」の関係です。

さらに9行目で、doubled という新しい変数を宣言し、evens.map という高階関数を使って、evens 内の要素を2倍した値(num => num * 2 した要素)を、新しい doubled の中に格納しています。

evens の中の2と4の要素が map 内の処理で2倍され、4と8になって新しい evens の中に格納されます。
この時の num => num % 2 === 0 や num => num * 2 の部分をコールバック関数と呼びます。

前項で学んだアロー関数が使用されているため、「引数 num を受け取り、偶数となる要素を返す」「引数 num を受け取り、num * 2 を返す」という処理が非常に短く簡潔に記述されています。これが関数として機能することで、高階関数にコールバック関数の処理結果が引き渡されています。

9: const doubled = evens.map(num => num * 2);

mapも内部動作を追ってみましょう。map は、配列の要素を1つずつ順番にコールバック関数に渡し、コールバック関数が返した値を集めて、新しい配列を作ります。
evens.map(num => num * 2) の場合、内部では以下のことが起きています。

この結果、新しい配列 [4, 8] が doubled に格納されます。

参考:filterの動きをfor文で書くと…

高階関数の動きがイメージしにくい場合は、for文で同じことを書いた場合と比べてみましょう。

// for文で書いた場合(filterと同じ動き)
const numbers = [1, 2, 3, 4, 5];
const evens = [];
for (let i = 0; i < numbers.length; i++) {
    const num = numbers[i];        // ← 配列から1つずつ取り出す
    if (num % 2 === 0) {           // ← 判定する(コールバック関数の役割)
        evens.push(num);           // ← trueなら新しい配列に追加
    }
}

// filterで書いた場合(上のfor文と同じ動き)
const evens = numbers.filter(num => num % 2 === 0);

for文の「配列から1つずつ取り出す → 判定する → 条件に合えば新しい配列に追加する」という3つの処理を、filterは1行で実現しています。filterが「ループの枠組み」を担当し、num => num % 2 === 0 というコールバック関数が「判定基準」だけを担当する、という役割分担になっています。

研修の詳細はこちら