では「関数(クロージャ・高階関数・メモ化・this など)」をテーマにした、前回の5問に対応する 追加練習問題(各1〜2問)+ステップ実行付きの丁寧な解説 を紹介します。
1. クロージャの応用 — 2つのカウンターが独立するか?
問題
次のコードの出力を予想し、なぜそうなるかを説明してください。
function makeCounter() {
let count = 0;
return function() {
count++;
console.log(count);
}
}
const a = makeCounter();
const b = makeCounter();
a();
a();
b();
a();
JavaScriptステップ実行解説
| ステップ | 実行内容 | count の所属 | aのcount | bのcount | 出力 |
|---|---|---|---|---|---|
| 1 | makeCounter() 呼び出し → クロージャ作成 | a専用の count=0 | 0 | — | — |
| 2 | makeCounter() 呼び出し(2回目) | b専用の count=0 | 0 | 0 | — |
| 3 | a() 実行 | aの環境を使用 | 1 | 0 | 1 |
| 4 | a() 実行 | aの環境 | 2 | 0 | 2 |
| 5 | b() 実行 | bの環境 | 2 | 1 | 1 |
| 6 | a() 実行 | aの環境 | 3 | 1 | 3 |
出力結果
1
2
1
3
解説
makeCounterは呼び出すたびに 新しい閉包環境(count変数) を作る。- よって、
aとbは独立した内部状態を持ち、互いに干渉しない。 - クロージャは「状態を持った関数」を生成する便利なパターン。
2. 可変長引数+reduce 応用 — 平均値を求める関数
問題
average(...nums) を定義し、任意の個数の数値の平均値を返す関数を作ってください。
出力例:
console.log(average(2, 4, 6, 8)); // => 5
JavaScript解答例
function average(...nums) {
let total = nums.reduce((a, b) => a + b, 0);
return total / nums.length;
}
JavaScriptステップ実行解説(average(2,4,6,8))
| ステップ | 変数 | 値 | 説明 |
|---|---|---|---|
| 1 | nums | [2,4,6,8] | ...で配列化 |
| 2 | reduce1 | a=0, b=2 → 2 | 初期値0から加算 |
| 3 | reduce2 | a=2, b=4 → 6 | 累積6 |
| 4 | reduce3 | a=6, b=6 → 12 | 累積12 |
| 5 | reduce4 | a=12, b=8 → 20 | 累積20 |
| 6 | total = 20, nums.length=4 | — | |
| 7 | return 20/4 → 5 | 平均計算 |
出力結果
5
解説
- 可変長引数と
reduceの組み合わせは「集計」処理に強い。 - データ分析系では平均・分散・最大値などもこの流れで実装できる。
3. 高階関数 — 条件付き関数生成
問題
数値が正ならそのまま返し、負なら0を返す「安全加算関数」を生成する高階関数を作ってください。
function makeSafeAdder(limit) {
return function(x, y) {
const sum = x + y;
return sum > limit ? limit : sum;
}
}
const addMax10 = makeSafeAdder(10);
console.log(addMax10(3, 4)); // => 7
console.log(addMax10(8, 5)); // => 10
JavaScriptステップ実行(addMax10(8,5))
| ステップ | 関数 | 変数 | 値 | 説明 |
|---|---|---|---|---|
| 1 | makeSafeAdder(10) 呼出 | limit | 10 | クロージャ生成 |
| 2 | addMax10(8,5) 呼出 | x=8, y=5 | — | 呼出開始 |
| 3 | sum = 13 | — | — | 計算 |
| 4 | 比較 13 > 10 → true | — | — | |
| 5 | return 10 | — | 出力10 |
出力結果
7
10
解説
- クロージャ+条件分岐を組み合わせると、「動的にルールを持った関数」を生成できる。
- UI制御やバリデーションロジックを動的生成するのにも使える。
4. メモ化再帰の確認 — キャッシュが効いているか見える化
問題
fib(6) の計算中に、キャッシュ利用の様子を出力してください。
function memoFib() {
const cache = {};
return function fib(n) {
if (n in cache) {
console.log("cache hit:", n);
return cache[n];
}
if (n <= 1) return n;
cache[n] = fib(n-1) + fib(n-2);
return cache[n];
};
}
const fib = memoFib();
console.log(fib(6));
JavaScriptステップ実行結果(出力例)
cache hit: 2
cache hit: 3
cache hit: 4
cache hit: 5
8
ステップ解説
| 呼出 | 動作 | cache の中身 |
|---|---|---|
| fib(6) | 初回計算 | {} |
| fib(5) | fib(4)+fib(3) | — |
| fib(4) | fib(3)+fib(2) | — |
| fib(3) | fib(2)+fib(1) | — |
| fib(2) | fib(1)+fib(0) | — |
| 以後戻りながらcache登録 | {2:1,3:2,4:3,5:5} | |
| 再利用時 | 「cache hit: n」 出力 |
解説
in cacheでキャッシュ確認。- 再帰関数の最適化を目で追うのに最適な練習問題。
- 大きな n(例:40)で試すと速度差が体感できる。
5. thisの理解 — setTimeout内のthisを修正せよ
問題
次のコードの出力を修正して "User: Alice" が表示されるようにしなさい。
const user = {
name: "Alice",
greet() {
setTimeout(function() {
console.log("User:", this.name);
}, 1000);
}
};
user.greet();
JavaScript① 問題点
- 通常関数の中では
thisが失われるため、this.nameはundefinedになる。
② 修正例(bindを使用)
greet() {
setTimeout(function() {
console.log("User:", this.name);
}.bind(this), 1000);
}
JavaScript③ 修正版(アロー関数使用)
greet() {
setTimeout(() => {
console.log("User:", this.name);
}, 1000);
}
JavaScriptステップ実行(アロー関数版)
| ステップ | 説明 |
|---|---|
| 1 | user.greet() 呼び出し。this は user。 |
| 2 | setTimeout に渡すアロー関数が生成される。この時点で this は レキシカル束縛 → user を記憶。 |
| 3 | 1秒後、アロー関数実行 → this.name は "Alice"。 |
| 4 | 出力 "User: Alice"。 |
出力
User: Alice
解説
- アロー関数は外側スコープの
thisを保持するため、コールバック内でthisが変わらない。 - コールバック処理(イベント・タイマー)で頻出する
this問題の定番解決法。
まとめ
| テーマ | 新問題で学ぶ要点 |
|---|---|
| クロージャ | 関数ごとに独立した状態を保持できる |
| 可変長+reduce | 配列操作で集計処理を行うパターン |
| 高階関数 | 条件付き関数生成・動的ロジック構築 |
| メモ化 | キャッシュ再利用で計算効率アップ |
| this | bind / アロー関数でのコンテキスト維持 |

