JavaScript | コールバック関数 と 高階関数

JavaScript JavaScript
スポンサーリンク

JavaScript は 関数を普通の値(変数に入れられる・引数に渡せる・戻り値にできる) として扱えます。
この性質を使うと「ある処理が終わったら別の処理を呼ぶ」といった柔軟な書き方ができます。まず用語から押さえましょう。

用語

  • コールバック関数(callback):別の関数に渡され、その関数の中で後から呼ばれる関数。
  • 高階関数(higher-order function):関数を引数に取ったり、関数を返したりする関数。

基本イメージ(超かんたん)

「関数を手紙だとすると、関数を渡す = 手紙を誰かに渡す。受け取った人(関数)がその手紙(関数)を中で開いて使う」
→ 渡した関数は「後で呼ばれる(コールバック)」。


例1 — 関数を変数に入れる(基礎)

const sayHi = function(name) {
  console.log('こんにちは、' + name + 'さん!');
};

sayHi('太郎'); // => こんにちは、太郎さん!
JavaScript

ポイント:sayHi は「関数を参照する変数」。sayHi() で実行します。


例2 — 関数を引数として渡す(コールバック)

function greet(name, formatter) {
  // formatter は「関数」が来る想定
  const message = formatter(name);
  console.log(message);
}

function makeHello(n) {
  return 'Hello, ' + n + '!';
}

// 関数を渡して呼ぶ
greet('花子', makeHello); // => Hello, 花子!
JavaScript

流れ:

  1. greetnameformatter を渡す
  2. greet の中で formatter(name) を呼ぶ → makeHello が実行される

ステップ実行(例2 を図で追う)

呼び出し:greet('花子', makeHello)

  1. greet の引数 name = '花子'formatter = makeHello(関数参照)
  2. const message = formatter(name);makeHello('花子') 実行 → 文字列 'Hello, 花子!' を返す
  3. console.log(message) で出力

例3 — その場で関数を作って渡す(匿名関数 / アロー関数)

greet('次郎', (n) => 'やあ、' + n + '!'); // => やあ、次郎!
JavaScript

メリット:小さな処理を使い捨てで素早く書ける。


例4 — 非同期のとき(setTimeout)

非同期処理(時間後に実行するなど)でよく使います。

console.log('開始');
setTimeout(() => {
  console.log('3秒後に表示');
}, 3000);
console.log('終了');

// 出力順: '開始' → '終了' → (3秒後)'3秒後に表示'
JavaScript

ポイント:setTimeout に渡した関数が「3秒後に呼ばれる」=コールバック。


例5 — 配列メソッドにもコールバックがいっぱい(map, filter, forEach)

const nums = [1, 2, 3, 4];

// map は「要素ごとに関数を呼んで新しい配列を返す」
const doubled = nums.map(x => x * 2); // [2,4,6,8]

// filter は「関数が true の要素だけ残す」
const evens = nums.filter(x => x % 2 === 0); // [2,4]

// forEach は「各要素で関数を実行(戻り値は無視)」
nums.forEach(x => console.log(x));
JavaScript

これらのメソッドは 高階関数(関数を引数に取る)です。


もう少し高度:高階関数が関数を返す例(カリー化風)

function makeAdder(x) {
  // 関数を返す(返された関数は x を覚えている)
  return function(y) {
    return x + y;
  };
}

const add5 = makeAdder(5);
console.log(add5(3)); // => 8
JavaScript

ポイント:関数が関数を返すことで、設定済みの関数(設定済みの動作) を作れます。


よくある間違い・注意点

  1. 関数そのものを渡す vs 実行結果を渡す
    • setTimeout(doSomething, 1000) は OK(関数参照を渡す)
    • setTimeout(doSomething(), 1000) は NG(関数を即実行してその戻り値を渡す)
  2. this の取り扱い
    • コールバック内の this は呼び出し側による。特にメソッドをそのまま渡すと this が変わることがある → .bind() やアロー関数で対処。
  3. コールバック地獄(callback hell)
    • コネクテッドな非同期処理をネストで書くと読みづらくなる。Promiseasync/await を使うと読みやすくなる。
  4. エラーハンドリング
    • 非同期コールバックでは例外が消えることがある(環境による)。Promise の方がエラー伝播が扱いやすい。

実践的な例 — ボタンを押したら処理(ブラウザ)

<button id="btn">押してね</button>
<script>
  const btn = document.getElementById('btn');
  btn.addEventListener('click', function(event) {
    alert('ボタンが押されました!');
  });
</script>
JavaScript

addEventListener に渡している関数が コールバック。イベントが発生したときに呼ばれます。


練習問題

問題1

配列 ['apple','banana','cherry'] を受け取り、各要素の先頭に "果物: " を付けた新しい配列を作る関数 labelFruits(arr, func) を作り、func は要素を受け取って変換した文字列を返すコールバックにしてください。labelFruits は変換後の配列を返すこと。

問題2

waitAndRun(ms, func) という関数を作り、ms ミリ秒待ったあと func() を呼ぶ。実行例を示してください。

問題3(発展)

repeat(n, func) を作り、nfunc(i) を呼ぶ(i は 0 から n-1 のインデックス)。それを使って 5 回コンソールに Hello 0Hello 4 を出力する。


解答例(模範解答)と説明

解答1

function labelFruits(arr, func) {
  return arr.map(item => func(item));
}

const fruits = ['apple','banana','cherry'];
const result = labelFruits(fruits, item => '果物: ' + item);
console.log(result);
// => ['果物: apple', '果物: banana', '果物: cherry']
JavaScript

説明:labelFruits は内部で map を使い、渡された func を各要素に適用して新しい配列を返します。

解答2

function waitAndRun(ms, func) {
  setTimeout(func, ms);
}

waitAndRun(1000, () => console.log('1秒後に実行されました'));
JavaScript

説明:setTimeout にコールバック関数を渡すことで指定時間後に実行されます。

解答3

function repeat(n, func) {
  for (let i = 0; i < n; i++) {
    func(i);
  }
}

repeat(5, i => console.log('Hello ' + i));
JavaScript

説明:repeat は高階関数。渡した func を複数回(ループ回数分)呼びます。


次のステップ(推奨)

  • 短いコードを書いて console.log で実行順を観察する(同期 vs 非同期 が実感できます)。
  • コールバックが複雑になってきたら Promiseasync/await を学ぶと可読性が大きく向上します。
  • 実際のAPI呼び出し(fetch)でコールバック/Promise の挙動を練習してみる。

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