高階関数・コールバック関数とは?

この記事では、JavaScript中級レベルで出てくる高階関数・コールバック関数について、分かりやすく解説します。名前は難しい感じがしますが、一度理解すれば簡単です。

高階関数・コールバック関数の定義

まず、2つの語句の定義を確認しましょう。

  • 高階関数とは、関数を引数として受け取ったり、関数を戻り値として返したりする関数
  • コールバック関数は、他の関数に引数として渡される関数

もっとシンプルに書くと、高階関数は関数を使う側で、コールバック関数は使われる側です。
なかなか言葉だけでは、イメージがつかみにくいと思うので、ストーリー形式でイメージを掴んでいきましょう。

OZ
OZ

先生〜!今日はなんか難しそうな『高階関数』とか『コールバック関数』について教えてもらえるんですよね?

そうです。難しく聞こえるかもしれないですが、実は、私たちの普段の生活にもよく似た考え方があるんです。

えっ、マジですか!?どういうことですか?

例えば、お母さんに『お使いに行ってきて』って言われたことありますよね?

あーあります!『ついでに野菜も買ってきて』みたいな感じですよね♪

そうです!これって『お使いという関数』の中に『買い物という関数』を渡しているようなものなんです。これが高階関数コールバック関数の基本的な考え方です。
というわけで、簡単なコードの例から見ていきましょう。

// 通常の関数
function greet(name) {
    console.log(`こんにちは、${name}さん!`);
}

greet("OZ");  // 出力: こんにちは、OZさん!

これは普通の関数ですよね。では、これを高階関数を使って書き換えてみましょう!

高階関数とコールバック関数のコード例

// 高階関数の例
function greetWithCallback(name, callback) {
    // callbackという関数を受け取って実行する
    callback(name);
}

// コールバック関数として渡す関数
function sayHello(name) {
    console.log(`こんにちは、${name}さん!`);
}

// 関数を引数として渡す
greetWithCallback("OZ", sayHello);  // 出力: こんにちは、OZさん!

おぉ!!なんか関数の中に関数を入れてる感じですね!
でも、これのどこがええんですか?

いい質問ですね!例えば、挨拶の仕方を簡単に変更できるんです!

// 高階関数の例
function greetWithCallback(name, callback) {
    // callbackという関数を受け取って実行する
    callback(name);
}

// 関西弁バージョンのコールバック関数
function sayHelloKansai(name) {
    console.log(`やっほー、${name}くん!元気かいな!`);
}

// 丁寧バージョンのコールバック関数
function sayHelloPolite(name) {
    console.log(`いつもお世話になっております、${name}様`);
}

// 同じ高階関数で違う挨拶ができる
greetWithCallback("OZ", sayHelloKansai);    // 出力: やっほー、OZくん!元気かいな!
greetWithCallback("OZ", sayHelloPolite);    // 出力: いつもお世話になっております、OZ様

おお!なるほど!同じ関数でも、渡す関数によって動作が変わるんですね!

そうです!これが高階関数コールバック関数の魅力なんです!このとき、greetWithCallback関数が高階関数で、sayHelloKansai関数やsayHelloPolite関数が、コールバック関数に当たります。sayHelloKansai関数やsayHelloPolite関数は、greetWithCallback関数に呼ばれてやってきた感じがしますよね。コールバックとは、「かけ直してもらう」という意味ですから、自分で直接実行するのではなく、相手に実行してもらう関数と考えることができますよね。これで覚えやすくなりましたか?では、コールバック関数のもう少し実践的な例も見てみましょう!

高階関数とコールバック関数の実践的な例(map関数)

// 配列の要素を処理する高階関数
const numbers = [1, 2, 3, 4, 5];

// 各要素を2倍にする関数
function double(num) {
    return num * 2;
}

// 各要素を3倍にする関数
function triple(num) {
    return num * 3;
}

// mapは高階関数の一つ
const doubledNumbers = numbers.map(double);
console.log(doubledNumbers);  // 出力: [2, 4, 6, 8, 10]

const tripledNumbers = numbers.map(triple);
console.log(tripledNumbers);  // 出力: [3, 6, 9, 12, 15]

上記のコードでは、map関数が高階関数、double関数やtriple関数がコールバック関数に当たります。

うぉ〜!!これめっちゃ便利ですやん!配列の中身を好きなように変換できるんですね!

そうなんです!実は、map以外にもfilterreduceなど、JavaScriptには便利な高階関数がたくさんあるんです。じゃあ、次は、filterを見てみましょう!

高階関数とコールバック関数の実践的な例(filter関数)

const scores = [45, 80, 90, 35, 70, 65];

// 合格点(60点以上)かどうかを判定する関数
function isPassing(score) {
    return score >= 60;
}

// filterは条件に合う要素だけを残す高階関数
const passingScores = scores.filter(isPassing);
console.log(passingScores);  // 出力: [80, 90, 70, 65]

おぉ!!これも便利ですね!!テストの点数とか、簡単に合格した人だけ抽出できますね!

その通りです!このように、高階関数とコールバック関数を使うと、コードがとても柔軟で再利用しやすくなります!そして、さらにもう1つ!超便利な高階関数として、reduceというものがあります!

リデュース?減らすって意味ですか?

確かに、単語を日本語訳すると「減らす」って意味ですが、JavaScriptの世界では、「配列の要素を1つにまとめる時に使う関数」です。配列を1つにまとめて、結果的に減らしていますよね。このreduceは、例えば、テストの合計点を計算したい時とかに便利なんです。

高階関数とコールバック関数の実践的な例(reduce関数)

const scores = [45, 80, 90, 35, 70, 65];

// reduceを使って合計を計算
const totalScore = scores.reduce((sum, score) => {
    return sum + score;
}, 0);

console.log(totalScore);  // 出力: 385

なるほど!!結果として、1つに合計したのは分かるけど、コードの構造はどうなってるんですか?

そうですね。reduceは少し特殊な書き方をします。1つ1つ解説していきますね。

const scores = [45, 80, 90, 35, 70, 65];

// reduceの基本形
scores.reduce((累計値, 現在の要素) => {
    return 新しい累計値;
}, 初期値);

例えば、1回目の処理では、

  • 累計値(sum)は初期値の0
  • 現在の要素(score)は45
  • 返す値は 0 + 45 = 45

2回目の処理では、

  • 累計値は45
  • 現在の要素は80
  • 返す値は 45 + 80 = 125

というように、配列の最後まで処理を繰り返すんです!

なるほど!!よく分かりました!!じゃあ、reduceを使えば、平均点なんかも計算することができるんじゃないですか?

そのとおりです!その場合は、こんな感じになります!

const scores = [45, 80, 90, 35, 70, 65];

// reduceを使って平均点を計算
const average = scores.reduce((sum, score) => {
    return sum + score;
}, 0) / scores.length;

console.log(average);  // 出力: 64.16666666666667

おぉ!!すごい!!他にも、reduceを使える例ってありますか?

ありますよ。例えば、科目ごとの点数を合計する時なんかにも使えますよ。

const subjectScores = [
    { subject: '数学', score: 80 },
    { subject: '英語', score: 75 },
    { subject: '国語', score: 90 },
    { subject: '数学', score: 85 },
    { subject: '英語', score: 70 }
];

// 科目ごとの合計点を計算
const totalBySubject = subjectScores.reduce((result, current) => {
    // 科目がまだresultに無ければ初期化
    if (!result[current.subject]) {
        result[current.subject] = 0;
    }
    // 点数を加算
    result[current.subject] += current.score;
    return result;
}, {});

console.log(totalBySubject);
// 出力: { '数学': 165, '英語': 145, '国語': 90 }

では、上記のコードで、reduceを使った処理を見ていきますよ!

const totalBySubject = subjectScores.reduce((result, current) => {

ここでは、reduceの引数として、

  • result: これまでの集計結果を保持するオブジェクト
  • current: 現在処理している配列の要素(テストの結果)

を受け取っています!

if (!result[current.subject]) {
        result[current.subject] = 0;
    }

このif文は、新しい科目が出てきた時の処理になります!

  • current.subjectで現在の科目名(’数学’など)を取得
  • result[current.subject]でその科目の合計点を参照

もし合計点がまだ無ければ(つまり初めての科目なら)、0で初期化しています!

ちなみに、ここで補足ですが、JavaScriptのオブジェクトでは、キー名に値を代入すると、そのキーと値のペアが作られます。

// 空のオブジェクトを作る
const obj = {};
console.log(obj);  // 出力: {}

// キー名 'math' に値 0 を代入
obj['math'] = 0;
console.log(obj);  // 出力: { math: 0 }

// キー名 'english' に値 100 を代入
obj['english'] = 100;
console.log(obj);  // 出力: { math: 0, english: 100 }

// 既存のキー 'math' に新しい値を代入
obj['math'] = 80;
console.log(obj);  // 出力: { math: 80, english: 100 }

これは次の書き方でも同じです。

const obj = {};

// ドット記法でも同じことができます
obj.math = 0;
console.log(obj);  // 出力: { math: 0 }

// ブラケット記法([])とドット記法(.)は同じ結果になります
obj['english'] = 100;  // ブラケット記法
obj.science = 90;      // ドット記法
console.log(obj);  // 出力: { math: 0, english: 100, science: 90 }

だから、result[current.subject] = 0 は、

  1. current.subject(例:’数学’)がキー名になる
  2. そのキーに値として0が代入される
  3. 結果として { '数学': 0 } のようなオブジェクトが作られる

という処理が行われているんですね!

じゃあ、続きのコードを見ていきましょう!

result[current.subject] += current.score;

ここで実際の点数を加算していきます。

  • current.scoreで現在のテストの点数を取得
  • それをresult[current.subject](その科目の合計点)に足していきます。
return result;
}, {});

この部分は重要なポイントが2つあります!

  1. return resultで、更新した集計結果を次の処理に渡す
  2. 最後の{}は、集計結果を入れる空のオブジェクトを初期値として設定しています

なるほど!だから、最終的に、

// 出力: { '数学': 165, '英語': 145, '国語': 90 }

そうです!よって、こんな感じで科目ごとの合計点が計算できるんです!

数学:80 + 85 = 165

英語:75 + 70 = 145

国語:90(1回だけ) ですね。

なるほど!reduce恐るべし!めっちゃ便利ですね!すごい!!!

そう!このように、高階関数とコールバック関数は実務でもとても重要な概念なんだ。特によく使うのは、

  1. イベントハンドラの設定(ボタンクリックなど)
  2. 非同期処理(APIからのデータ取得など)
  3. 配列の処理(map, filter, reduceなど)
  4. バリデーション処理
  5. タイマーや遅延処理(setTimeout, setIntervalなど)

これらはWebアプリケーション開発では基本中の基本です。

へー!最初は難しそうに感じたけど、実際のコードを見ると結構理解できました!ありがとうございます!

まとめ

  • 高階関数は、関数を引数として受け取ったり、関数を戻り値として返したりする関数
  • コールバック関数は、他の関数に引数として渡される関数

これらを使うことで、コードの再利用性が高まり、柔軟な処理が可能になります。

JavaScriptの主な高階関数:

  • map: 配列の各要素を変換する
  • filter: 条件に合う要素だけを抽出する
  • reduce: 配列の要素を1つの値にまとめる(合計計算や複雑なデータの集計に便利)

これらの関数は、イベントハンドラ非同期処理バリデーションタイマーなど、Webアプリ開発でも頻繁に利用されます。最初は少し難しく感じるかもしれませんが、慣れてくると、より簡潔で保守性の高いコードが書けるようになります。ぜひ、さまざまな場面で使いこなしてみてください!