8-2. 演習問題
問題1:選択問題
Section titled “問題1:選択問題”1-1. 次のコードの実行結果として正しいものはどれか。
greet("Alice");
function greet(name) { return `Hello, ${name}`;}- A.
ReferenceError: greet is not defined - B.
"Hello, Alice" - C.
undefined - D.
TypeError: greet is not a function
解答と解説
正解:B
関数宣言(function greet() {})はホイスティング(巻き上げ)の対象になる。JavaScriptエンジンはコードを実行する前に関数宣言をスコープの先頭に「巻き上げる」ため、宣言より前に呼び出してもエラーにならない。
これに対し関数式(const greet = function() {})やアロー関数(const greet = () => {})はホイスティングされないため、宣言より前に呼ぶと ReferenceError または TypeError が発生する。
1-2. 次のコードの result の値はどれか。
const add = (a, b = 10) => a + b;const result = add(5);- A.
5 - B.
NaN - C.
15 - D.
undefined
解答と解説
正解:C
b にデフォルト引数 10 が設定されているため、add(5) は add(5, 10) と同じになり、5 + 10 = 15 が返る。
デフォルト引数は引数が渡されなかった場合、または明示的に undefined が渡された場合に使われる。
add(5) // 15(bにデフォルト値10が使われる)add(5, undefined) // 15(undefinedもデフォルト値が使われる)add(5, null) // 5(nullはundefinedではないのでデフォルト値は使われない)1-3. 次のコードの出力結果として正しいものはどれか。
function makeCounter() { let count = 0; return () => { count++; return count; };}
const counter = makeCounter();console.log(counter());console.log(counter());console.log(counter());- A.
1,1,1 - B.
1,2,3 - C.
0,1,2 - D. エラーが発生する
解答と解説
正解:B
これはクロージャの典型的な例だ。makeCounter() が返す関数は count 変数を「閉じ込めて」参照し続ける。counter を呼ぶたびに同じ count がインクリメントされるため、1, 2, 3 と増えていく。
makeCounter() を呼ぶ └── count = 0 のスコープが生まれる └── () => { count++; return count; } がこのスコープを保持して返る
counter() → count が 0→1 になり 1 を返すcounter() → count が 1→2 になり 2 を返すcounter() → count が 2→3 になり 3 を返すmakeCounter() を別々に呼ぶと独立したカウンターになる。
const c1 = makeCounter();const c2 = makeCounter();c1() // 1(c1専用のcount)c1() // 2c2() // 1(c2専用のcount、c1とは独立)1-4. 次のコードの出力結果はどれか。
{ let x = 1; var y = 2;}console.log(typeof x);console.log(typeof y);- A.
"number"と"number" - B.
"undefined"と"undefined" - C.
"undefined"と"number" - D. エラーが発生する
解答と解説
正解:C
let はブロックスコープのため、{} の外からは見えず typeof x は "undefined" になる(ReferenceError ではなく "undefined" になるのは typeof の特殊な挙動)。
var はブロックスコープを持たず関数スコープのため、{} の外に漏れて typeof y は "number" になる。
これが var を使わない理由の一つだ。意図しない変数の漏れがバグの原因になる。
問題2:穴埋め問題
Section titled “問題2:穴埋め問題”2. 次のコードの( )に入るコードを答えよ。
// (1) 引数 n を受け取り、n の二乗を返すアロー関数const square = (1);
// (2) 可変長引数を受け取り、最大値を返す関数function max((2)nums) { return Math.max((2)nums);}max(3, 1, 4, 1, 5, 9) // 9
// (3) クロージャを使った挨拶文生成関数function makeGreeter(greeting) { return (3)(name) { return `${greeting}, ${name}!`; };}const hello = makeGreeter("Hello");hello("World") // "Hello, World!"解答と解説
n => n * n(または(n) => n ** 2)...(スプレッド構文 / 残余引数)function(または=>)
解説
(1) アロー関数の短縮形。引数1つ・式1つなら (n) => { return n * n; } の括弧・波括弧・return を全て省略できる。
(2) ...nums で可変長引数を配列として受け取る(残余引数)。Math.max(...nums) では配列を個別の引数に展開するスプレッド構文として使われている。同じ ... 記号が「収集」と「展開」の両方に使われることに注意。
(3) function キーワードかアロー関数 (name) => ... のどちらでも正解。クロージャにより greeting が内側の関数に閉じ込められ、makeGreeter が返った後も参照できる。
問題3:記述問題
Section titled “問題3:記述問題”3-1. 次の for ループで使っている「2倍にする処理」をアロー関数 doubleNumber として切り出し、ループの中から呼び出す形に書き直せ。
const numbers = [1, 2, 3, 4, 5];const doubled = [];for (let i = 0; i < numbers.length; i++) { doubled.push(numbers[i] * 2);}解答と解説
アロー関数を切り出した書き直し:
const numbers = [1, 2, 3, 4, 5];const doubled = [];const doubleNumber = n => n * 2;
for (let i = 0; i < numbers.length; i++) { doubled.push(doubleNumber(numbers[i]));}アロー関数へ切り出すと、「2倍にする」という処理名を与えられるため、ループの中で何をしているかを読み取りやすくなる。配列を回す仕組みはまだ for のままにしておき、ここでは 関数を値として扱えること と 処理を再利用できること に注目する。
3-2. クロージャを使ってプライベート変数を実現するコードを書け。increment()、decrement()、getCount() の3つのメソッドを持つカウンターオブジェクトを返す createCounter 関数を実装せよ。
解答と解説
function createCounter() { let count = 0; // 外から直接アクセスできないプライベート変数
return { increment: function() { count++; }, decrement: function() { count--; }, getCount: function() { return count; }, };}
const counter = createCounter();counter.increment();counter.increment();counter.decrement();console.log(counter.getCount()); // 1console.log(counter.count); // undefined(外からは見えない)なぜこれがプライベートか:
count は createCounter のスコープ内にあり、返されたオブジェクトのメソッドからしかアクセスできない。counter.count のように外から直接参照しようとしても undefined になる。
これをモジュールパターンと呼ぶ。クラスの private と似た効果をクロージャで実現している。
問題4:ハンズオン
Section titled “問題4:ハンズオン”createCounter の各メソッドを実装し、クロージャで状態を保持できているか確認せよ。
取り組む内容
Section titled “取り組む内容”increment・decrement・resetの TODO を実装するconsole.logの出力を実行前に予想し、実際の結果と比較するcreateCounter(10)の引数を変えてみて、挙動が変わることを確かめる
確認したいポイント
Section titled “確認したいポイント”countが関数の外から直接見えないのに、メソッドからは参照できる理由reset()が現在値ではなく最初のstartに戻る理由increment(5)とdecrement(3)の結果を順に追えるか