高階関数の基本 (map, filter, reduce…など)

jsコード JavaScript

関数型プログラミングの核心には「高階関数」(Higher-Order Functions)の存在があります。
高階関数は、関数を引数として受け取ったり、関数を返すことができる関数のことを指します。

JavaScriptでは、この高階関数を使用することで、コードの再利用性と柔軟性を大幅に向上させることができます。

本記事では、高階関数の基本的な概念と使用方法について詳しく説明します。

高階関数とは

高階関数とは、以下のいずれかの条件を満たす関数です。

  • 関数を引数として受け取る関数
  • 関数を戻り値として返す関数

高階関数を使用することで、関数をより汎用的かつ柔軟に扱うことができます。

本記事で紹介する高階関数

JavaScriptには、配列操作のための高階関数がいくつか用意されています。

その代表例がmap, filter, reduceですが、本記事ではその他の重要な高階関数(some, every, find, findIndex, sort, forEach)も併せて紹介します。

map

mapは、Array.prototype.map として定義されています。

関数を引数として受け取り、配列の各要素に対して関数を適用し、新しい配列を生成して返します

mapの使用例

const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map( n => n * 2 );

console.log(doubled); // [2, 4, 6, 8, 10]

👆上記の例では、mapを使用して、配列内の各要素を2倍にしています。

mapが受け取っている引数は、関数(アロー関数)です。
この関数が配列内の要素を1つ1つ2倍にして、新しい配列として生成しています。

filter

filterは、Array.prototype.filterとして定義されています。

配列の各要素に対して関数を適用し、指定された条件を満たす要素だけを含む新しい配列を生成します。文字通りフィルタリングしているので、覚えやすいですね。

filterの使用例

const numbers = [1, 2, 3, 4, 5, 6];
const evenNumbers = numbers.filter( n => n % 2 === 0 );

console.log(evenNumbers); // [2, 4, 6]

👆上記の例では、filterを使用して、配列内の偶数だけを抽出しています。

filterが受け取っている引数は、関数(アロー関数)です。
この関数が配列内の要素を1つ1つ2で割って余りが0になるもの、つまりは偶数のみを選定し、新しい配列として生成しています。

reduce

reduceは、Array.prototype.reduceとして定義されています。

reduceは、配列を単一の値に変換するための高階関数です。
各要素に対して累積的に関数を適用し、最終的な結果を返します。

reduceの使用例

const numbers = [1, 2, 3, 4];
const sum = numbers.reduce( (total, n) => total + n, 0 );

console.log(sum); // 10

👆上記の例では、reduceを使用して、配列内の数値の合計を計算しています。

受け取っているアロー関数の内容が少々難しいので、仕組みと処理の流れを説明します。

サンプルプログラムの詳細

reduceの引数と初期設定

  • reduceには2つの引数が渡されます。
    • 第一引数はコールバック関数。今回の場合は「(total, n) => total + n」👈このアロー関数
    • 第二引数は初期値で、今回の場合は0
  • 初期設定
    • totalは累積計算の結果を保持する変数で、初期値0で開始します。
    • nは現在の配列の要素です。

各ステップの処理

  1. 最初のステップ
    • total = 0(初期値)
    • n = 1(配列の最初の要素)
    • total + n = 0 + 1 = 1
    • 新しいtotalの値は1
  2. 次のステップ
    • total = 1
    • n = 2(配列の次の要素)
    • total + n = 1 + 2 = 3
    • 新しいtotalの値は3
  3. 次のステップ
    • total = 3
    • n = 3(配列の次の要素)
    • total + n = 3 + 3 = 6
    • 新しいtotalの値は6
  4. 最後のステップ
    • total = 6
    • n = 4(配列の次の要素)
    • total + n = 6 + 4 = 10
    • 新しいtotalの値は10

some

someは、Array.prototype.someとして定義されています。

someは、配列の少なくとも1つの要素が指定された条件を満たす場合にtrueを返し、すべての要素が条件を満たさない場合にはfalseを返します。

someの使用例

trueが返る例
const numbers = [1, 2, 3, 4, 5];
const hasEven = numbers.some( n => n % 2 === 0 );

console.log(hasEven); // true

👆上記の例では、numbers配列に偶数(2と4)が含まれているため、sometrueを返します。

falseが返る例
const numbers = [1, 3, 5, 7, 9];
const hasEven = numbers.some( n => n % 2 === 0 );

console.log(hasEven); // false

👆上記の例では、配列numbersのいずれの要素も偶数(2で割り切れる)ではないため、somefalseを返します。

every

everyは、Array.prototype.everyとして定義されています。

everyメソッドは、配列のすべての要素が指定された関数の条件を満たすかどうかを確認します。
すべての要素が条件を満たす場合にtrueを返し、一つでも条件を満たさない要素がある場合はfalseを返します。

everyの使用例

trueが返る例
const numbers = [2, 4, 6, 8];
const allEven = numbers.every( n => n % 2 === 0 );

console.log(allEven); // true

👆上記の例では、配列numbersのすべての要素が偶数(2で割り切れる)であるため、everytrueを返します。

falseが返る例
const numbers = [2, 3, 6, 8];
const allEven = numbers.every( n => n % 2 === 0 );

console.log(allEven); // false

👆上記の例では、配列mixedNumbersに条件を満たさない要素(3)が含まれているため、everyfalseを返します。

find

findは、Array.prototype.findとして定義されています。

findは、配列の中から指定された関数の条件を満たす最初の要素を返します。条件を満たす要素が見つからない場合はundefinedを返します。

条件を満たす要素が複数ヒットしても、返されるのは最初の要素だけであることに注意してください。

findの使用例

const numbers = [1, 2, 3, 4, 5];
const firstEven = numbers.find( n => n % 2 === 0 );

console.log(firstEven); // 2

👆上記の例では、配列numbersの要素中2と4が条件に合致しますが、返されるのは条件を満たす最初の要素だけなので2を返します。

findIndex

findIndexは、Array.prototype.findIndexとして定義されています。

findIndexは、配列の中から指定された関数の条件を満たす最初の要素のインデックスを返します。条件を満たす要素が見つからない場合は-1を返します。

findIndexの使用例

const numbers = [1, 2, 3, 4, 5];
const firstEvenIndex = numbers.findIndex( n => n % 2 === 0 );

console.log(firstEvenIndex); // 1

👆上記の例では、配列numbersの要素中2と4が条件に合致しますが、返されるのは条件を満たす最初の要素のインデックスだけなので要素「2」のインデックス(添え字)である1を返します。

sort

sortは、Array.prototype.sortとして定義されています。

sortは、配列の要素を指定された関数に基づいてソートします。デフォルトでは文字列としてソートされますが、カスタムのソート関数を指定することも可能です。

sortの使用例(昇順ソート)

const numbers = [4, 2, 5, 1, 3];
numbers.sort( (a, b) => a - b );

console.log(numbers); // [1, 2, 3, 4, 5]

sortの使用例(降順ソート)

数値を降順にソートするためには、ソート関数として (a, b) => b - a を使用します。

const numbers = [4, 2, 5, 1, 3];
numbers.sort( (a, b) => b - a );

console.log(numbers); // [5, 4, 3, 2, 1]

カスタムソートのサンプル①

文字列をアルファベット順(昇順)にソートする場合は、デフォルトのsort関数をそのまま使用できますが、大文字小文字を区別しないカスタムソート関数を指定するサンプルを示します。

const fruits = ['Banana', 'apple', 'Cherry', 'date'];
fruits.sort( (a, b) => a.toLowerCase().localeCompare(b.toLowerCase()) );

console.log(fruits); // ['apple', 'Banana', 'Cherry', 'date']

カスタムソートのサンプル②

オブジェクトのプロパティを基にソートする場合も、カスタムソート関数を使用します。
例えば、オブジェクト配列を特定のプロパティ値でソートする場合です。

const items = [
    { name: 'apple', quantity: 2 },
    { name: 'banana', quantity: 4 },
    { name: 'cherry', quantity: 1 }
];

// quantity(数量)に基づいて昇順にソート
items.sort( (a, b) => a.quantity - b.quantity );
console.log(items);
デベロッパーツールのコンソール
 (3) [{…}, {…}, {…}]
 ▶ 0: {name: 'cherry', quantity: 1}
 ▶ 1: {name: 'apple', quantity: 2}
 ▶ 2: {name: 'banana', quantity: 4}

forEach

forEachは、以下の3種類として定義されています。

  • Array.prototype.forEach
  • Map.prototype.forEach
  • Set.prototype.forEach

forEachは、コレクション(Array、Map、Set)の各要素に対して指定された関数を一度ずつ実行します。
戻り値はなく、副作用を発生させるために使用されることが多いです。

forEachの使用例

const numbers = [1, 2, 3, 4, 5];
numbers.forEach( n => console.log(n) );
デベロッパーツールのコンソール
> 1
> 2
> 3
> 4
> 5

forEachを繰り返し処理(ループ)で使用する際の注意点

forEachの特性

  1. 破壊的ではない
    • forEachは元の配列を変更しません。配列の各要素に対して指定された関数を実行するだけです。
  2. 返り値がない
    • forEachメソッド自体は何も返しません。副作用を発生させるために使用されることが多いです。
  3. 非同期操作に適さない
    • forEachは同期的に動作するため、非同期操作(例えば、async/await)との相性が良くありません。非同期処理を行う場合は、for...ofループやPromise.allなどを検討する方が良いです。

forEachの副作用

forEachは配列の要素自体を変更しないため、配列そのものに対する破壊的な変更は行いません。
しかし、forEach内で実行される関数が外部の状態を変更する場合、それが副作用となります。

例えば、関数内で他の変数を変更したり、ログを出力したり、データベースにアクセスしたりすることなどが該当します。

注意点

  1. breakやcontinueが使えない
    • forEachループ内で、breakcontinueを使用してループを中断することはできません。ループを中断したい場合は、forループやfor...ofループを使用しましょう。
  2. 非同期処理に注意
    • forEach内で非同期関数を使用する場合、期待通りに動作しないことがあります。非同期処理を行う場合は、for...ofループやPromise.allを使用することが推奨されます。
  3. パフォーマンスの考慮
    • forEachは大きなデータセットに対して使用するとパフォーマンスに影響を与えることがあります。他のループ構造やメソッド(map, reduceなど)を検討することが重要です。

まとめ

高階関数は、JavaScriptの関数型プログラミングにおいて重要な役割を果たします。
関数を引数として受け取るmap, filter, reduceを始めとして、forEach, some, every, find, findIndex, sortなど、さまざまな高階関数を利用することで、より柔軟で再利用性の高いコードを書くことができます。

それぞれの使いどころ

  • map filter は、新しい配列を生成する際に使います。
  • reduce は値をまとめたい場合に適します。
    • 「Promise + 順次処理」で使用される場合もあります。
  • some every は条件判定に便利です。
  • find findIndex は特定の要素を見つけたい場合に利用します。
  • sort は並び替えが必要な時に使えます。
  • forEach は単純な反復処理が必要な場合に便利です。

次回の記事では、関数の合成やカリー化など、さらに高度な関数型プログラミングのパターンについて探求していきます。


本記事についての質問、誤りの指摘、ご意見ご感想などありましたら、ぜひコメント頂ければ幸いです。

最後までお読みいただき、ありがとうございました。

『詳説 JavaScript』メニューに戻る

コメント

タイトルとURLをコピーしました