- Day 13:スコープとクロージャで「賢いカウンター」を作る
- スコープとは:変数が「どこから見えるか」のルール
- クロージャの基本:関数が「外の変数」を覚えている
- 実践:クロージャでカウンターを作る
- クロージャで「隠れた状態」を持つカウンターを作る
- 複数のカウンターを独立して持つ
- let / const とスコープの関係をもう一度整理する
- Day 13 練習テンプレート:スコープとクロージャを意識したカウンター
- Day 13のまとめ
- Day 13:スコープとクロージャで「小さな仕組み」を賢くする
- スコープの基本:変数が「どこから見えるか」のルール
- クロージャの基本:関数が「外の変数」を覚えている
- 課題1:クロージャでカウンターを作る
- 課題2:ID生成器をクロージャで作る
- 課題3:設定値管理をクロージャで作る
- Day 13 練習テンプレート:3つのクロージャパターン
- Day 13のまとめ
Day 13:スコープとクロージャで「賢いカウンター」を作る
Day 13では、プログラムの見えない裏側でとても重要な概念、スコープとクロージャを扱います。 そして実践として、increment() / decrement() / getCount() / reset() を持つ「カウンター」を作りながら、スコープとクロージャの感覚を体に染み込ませていきます。
スコープとは:変数が「どこから見えるか」のルール
グローバルスコープ
グローバルスコープは、「プログラム全体から見える場所」にある変数のことです。
// グローバルスコープの変数
let globalCount = 0;
function addOne() {
// どこからでも globalCount にアクセスできる
globalCount += 1;
}
addOne();
console.log("globalCount:", globalCount); // 1
TypeScriptポイント:
- グローバル変数は便利ですが、「どこからでも書き換えられる」ため、規模が大きくなるとバグの原因になりやすいです。
- できるだけ「必要な範囲だけで見える変数」にしていくことが、健全な設計につながります。
関数スコープ
関数スコープは、「関数の中だけで見える変数」のことです。
function run() {
// 関数スコープの変数
const message = "関数の中だけで見えるメッセージ";
console.log(message);
}
run();
// これはエラー(message は関数の外から見えない)
// console.log(message);
TypeScriptポイント:
- 関数の中で宣言した変数は、その関数の外からは見えません。
- 「外から見えない」ことで、他の部分から勝手に書き換えられない安全な状態を作れます。
ブロックスコープ(let / const)
let と const は、ブロックスコープを持ちます。 ブロックとは、{ ... } で囲まれた範囲のことです。
if (true) {
let x = 10;
const y = 20;
console.log("ブロック内:", x, y); // 10 20
}
// ブロックの外からは見えない
// console.log(x); // エラー
// console.log(y); // エラー
TypeScriptポイント:
letとconstで宣言した変数は、そのブロックの外からは見えません。varは関数スコープですが、let/constはブロックスコープです。- TypeScript / 現代のJavaScriptでは、基本的に
let/constを使うのが定石です。
クロージャの基本:関数が「外の変数」を覚えている
クロージャとは何か
クロージャは、「関数が、自分の外側のスコープにある変数を覚えている状態」のことです。
function createGreeter(name: string) {
// 外側のスコープの変数
const greeting = `こんにちは、${name}さん`;
// 内側の関数(クロージャ)
function greet() {
// 外側の greeting にアクセスできる
console.log(greeting);
}
return greet;
}
const greeter = createGreeter("Taro");
greeter(); // こんにちは、Taroさん
TypeScript重要ポイント:
greet関数は、createGreeterの中で定義されています。greetは、外側のgreeting変数にアクセスできます。createGreeterの呼び出しが終わっても、greeter(=greet)はgreetingを覚え続けています。これがクロージャです。
この「外側の変数を覚えている」という性質を使うと、外から直接触れられない「隠れた状態」を持つオブジェクトのようなものを作ることができます。 これが、今日のカウンター作りの核心になります。
実践:クロージャでカウンターを作る
まずは「素朴な」グローバルカウンター
最初に、グローバル変数を使ったカウンターを見てみます。
// グローバルなカウンター
let count = 0;
function increment() {
count += 1;
}
function decrement() {
count -= 1;
}
function getCount() {
return count;
}
function reset() {
count = 0;
}
increment();
increment();
console.log("count:", getCount()); // 2
reset();
console.log("count after reset:", getCount()); // 0
TypeScript一見すると問題なさそうですが、
countがグローバルに公開されている- どこからでも
count = 9999のように書き換えられてしまう
という弱点があります。
そこで、クロージャを使って「外から直接触れられないカウンター」を作る方向に進みます。
クロージャで「隠れた状態」を持つカウンターを作る
カウンターを生成する関数を作る
// カウンターを生成する関数
function createCounter() {
// この count は createCounter の中だけで見える
let count = 0;
// カウンター操作用の関数たち(クロージャ)
function increment() {
count += 1;
}
function decrement() {
count -= 1;
}
function getCount() {
return count;
}
function reset() {
count = 0;
}
// 操作用の関数をまとめて返す
return {
increment,
decrement,
getCount,
reset,
};
}
// カウンターを1つ作る
const counter = createCounter();
counter.increment();
counter.increment();
console.log("counter:", counter.getCount()); // 2
counter.decrement();
console.log("counter:", counter.getCount()); // 1
counter.reset();
console.log("counter:", counter.getCount()); // 0
TypeScript重要ポイント(ここはしっかり深掘り):
createCounterの中でlet count = 0;を宣言しています。このcountは、外から直接見えません。increment / decrement / getCount / resetは、すべてcountにアクセスできます。これがクロージャです。createCounter()が返すオブジェクトには、これらの関数が入っていますが、countそのものは返していません。- つまり、外からは
countに直接触れられず、「用意された関数を通してだけ」状態を変えたり参照したりできるようになっています。
これは、「カウンターの内部状態を隠蔽し、インターフェースだけ公開する」という、とても大事な設計パターンです。
複数のカウンターを独立して持つ
クロージャのもう一つの強みは、「同じ仕組みで、独立した状態をいくつも持てる」ことです。
const counterA = createCounter();
const counterB = createCounter();
counterA.increment();
counterA.increment();
counterB.increment();
console.log("counterA:", counterA.getCount()); // 2
console.log("counterB:", counterB.getCount()); // 1
TypeScriptポイント:
createCounter()を呼ぶたびに、新しいcountが作られます。counterAとcounterBは、それぞれ別のcountを持っています。- グローバル変数でカウンターを作ると、こうした「独立した複数の状態」を持つのが難しくなりますが、クロージャなら自然にできます。
let / const とスコープの関係をもう一度整理する
カウンターの例を通して、let / const とスコープの関係をもう一度確認しておきます。
function createCounter() {
// 関数スコープ内の変数(ブロックスコープでもある)
let count = 0;
function increment() {
// 同じスコープ内の count にアクセス
count += 1;
}
return {
increment,
};
}
TypeScriptcountはcreateCounter関数の中だけで見える変数です。incrementは、その「中だけで見える変数」にアクセスできます。createCounterの外からはcountに直接アクセスできません。
この「見える範囲」を意識してコードを書くことが、安全で読みやすいプログラムにつながります。
Day 13 練習テンプレート:スコープとクロージャを意識したカウンター
最後に、今日の内容をまとめた練習用テンプレートを載せておきます。 このままコピーして、ログを増やしたり、初期値を変えられるようにしたりしながら、スコープとクロージャの感覚を育ててみてください。
// day13-counter.ts
// Day 13:スコープとクロージャでカウンターを作る
// カウンターを生成する関数
function createCounter() {
// カウンターの内部状態(外からは直接見えない)
let count = 0;
// 1増やす
function increment(): void {
count += 1;
}
// 1減らす
function decrement(): void {
count -= 1;
}
// 現在の値を取得
function getCount(): number {
return count;
}
// 0にリセット
function reset(): void {
count = 0;
}
// 操作用の関数をまとめて返す
return {
increment,
decrement,
getCount,
reset,
};
}
// テスト用コード
console.log("=== カウンターテスト ===");
const counter1 = createCounter();
const counter2 = createCounter();
counter1.increment();
counter1.increment();
counter2.increment();
console.log("counter1:", counter1.getCount()); // 2
console.log("counter2:", counter2.getCount()); // 1
counter1.decrement();
console.log("counter1 after decrement:", counter1.getCount()); // 1
counter1.reset();
console.log("counter1 after reset:", counter1.getCount()); // 0
console.log("counter2 still:", counter2.getCount()); // 1
TypeScriptDay 13のまとめ
Day 13では、
- グローバルスコープ・関数スコープ・ブロックスコープという「変数がどこから見えるか」のルール
let/constがブロックスコープを持つこと- クロージャが「関数が外側の変数を覚えている状態」であること
- クロージャを使って、内部状態を隠蔽したカウンターを作る方法
- 複数のカウンターを独立して持てること
を、具体的なコードとともに体験しました。
スコープとクロージャは、最初は少し抽象的に感じるかもしれませんが、カウンターのような小さな例から少しずつ慣れていくと、「あ、こういうときに使うんだ」と感覚がつかめてきます。 ぜひ、初期値を指定できるカウンターや、最大値・最小値を持つカウンターなど、自分なりのバリエーションを作りながら、スコープとクロージャを手になじませていってください。
Day 13:スコープとクロージャで「小さな仕組み」を賢くする
Day 13では、プログラムの裏側を支えるとても重要な概念、スコープとクロージャを扱います。 課題は次の3つです。
- カウンター
- ID生成
- 設定値管理
これらを通して、
- グローバルスコープ
- 関数スコープ
- ブロックスコープ
let/const- クロージャの基本
を、初心者の方でもイメージしやすい形で、ステップバイステップで見ていきます。
スコープの基本:変数が「どこから見えるか」のルール
グローバルスコープ
グローバルスコープは、「プログラム全体から見える場所」にある変数のことです。
// グローバルスコープの変数
let globalCount = 0;
function addOne() {
// どこからでも globalCount にアクセスできる
globalCount += 1;
}
addOne();
console.log("globalCount:", globalCount); // 1
TypeScriptポイント:
- グローバル変数は便利ですが、「どこからでも書き換えられる」ため、規模が大きくなるとバグの原因になりやすいです。
- できるだけ「必要な範囲だけで見える変数」にしていくことが、健全な設計につながります。
関数スコープ
関数スコープは、「関数の中だけで見える変数」のことです。
function run() {
// 関数スコープの変数
const message = "関数の中だけで見えるメッセージ";
console.log(message);
}
run();
// これはエラー(message は関数の外から見えない)
// console.log(message);
TypeScriptポイント:
- 関数の中で宣言した変数は、その関数の外からは見えません。
- 「外から見えない」ことで、他の部分から勝手に書き換えられない安全な状態を作れます。
ブロックスコープ(let / const)
let と const は、ブロックスコープを持ちます。 ブロックとは、{ ... } で囲まれた範囲のことです。
if (true) {
let x = 10;
const y = 20;
console.log("ブロック内:", x, y); // 10 20
}
// ブロックの外からは見えない
// console.log(x); // エラー
// console.log(y); // エラー
TypeScriptポイント:
let/constで宣言した変数は、そのブロックの外からは見えません。- TypeScript / 現代のJavaScriptでは、基本的に
let/constを使うのが定石です。
クロージャの基本:関数が「外の変数」を覚えている
クロージャとは
クロージャは、「関数が、自分の外側のスコープにある変数を覚えている状態」のことです。
function createGreeter(name: string) {
// 外側のスコープの変数
const greeting = `こんにちは、${name}さん`;
// 内側の関数(クロージャ)
function greet() {
// 外側の greeting にアクセスできる
console.log(greeting);
}
return greet;
}
const greeter = createGreeter("Taro");
greeter(); // こんにちは、Taroさん
TypeScript重要ポイント:
greetはcreateGreeterの中で定義されていますが、外側のgreetingを覚えています。createGreeterの呼び出しが終わっても、greeter(=greet)はgreetingにアクセスできます。- この「外側の変数を覚えている」性質を使うと、外から直接触れられない内部状態を持つ仕組みを作れます。
この性質を使って、課題の3つ――カウンター・ID生成・設定値管理――を作っていきます。
課題1:クロージャでカウンターを作る
グローバル変数版との違いを意識する
まずは、グローバル変数を使ったカウンターを見てみます。
// グローバルなカウンター
let count = 0;
function increment() {
count += 1;
}
function decrement() {
count -= 1;
}
function getCount() {
return count;
}
function reset() {
count = 0;
}
TypeScriptこの形だと、どこからでも count = 9999 のように書き換えられてしまいます。 そこで、クロージャを使って「外から直接触れられないカウンター」を作る方向に進みます。
クロージャ版カウンター
// カウンターを生成する関数
function createCounter() {
// この count は createCounter の中だけで見える
let count = 0;
function increment(): void {
count += 1;
}
function decrement(): void {
count -= 1;
}
function getCount(): number {
return count;
}
function reset(): void {
count = 0;
}
// 操作用の関数をまとめて返す
return {
increment,
decrement,
getCount,
reset,
};
}
// カウンターを1つ作る
const counter = createCounter();
counter.increment();
counter.increment();
console.log("counter:", counter.getCount()); // 2
counter.decrement();
console.log("counter:", counter.getCount()); // 1
counter.reset();
console.log("counter:", counter.getCount()); // 0
TypeScript深掘りポイント:
countはcreateCounterの中だけで見える変数です。increment / decrement / getCount / resetは、すべてcountにアクセスできます。これがクロージャです。- 外からは
countに直接触れられず、「用意された関数を通してだけ」状態を変えたり参照したりできます。
課題2:ID生成器をクロージャで作る
次は、連番のIDを生成する仕組みを作ってみます。
グローバル変数版
let currentId = 0;
function generateId(): number {
currentId += 1;
return currentId;
}
console.log(generateId()); // 1
console.log(generateId()); // 2
TypeScriptこれも、currentId がグローバルに公開されているため、どこからでも書き換えられてしまいます。
クロージャ版ID生成器
// ID生成器を作る関数
function createIdGenerator() {
// この id は外から直接見えない
let id = 0;
function nextId(): number {
id += 1;
return id;
}
return {
nextId,
};
}
// 1つ目のID生成器
const idGenA = createIdGenerator();
console.log("A:", idGenA.nextId()); // 1
console.log("A:", idGenA.nextId()); // 2
// 2つ目のID生成器(別のカウンター)
const idGenB = createIdGenerator();
console.log("B:", idGenB.nextId()); // 1
console.log("B:", idGenB.nextId()); // 2
TypeScriptポイント:
createIdGenerator()を呼ぶたびに、新しいidが作られます。idGenAとidGenBは、それぞれ独立したIDカウンターを持っています。- グローバル変数を使わずに、「独立した複数のID生成器」を自然に作れるのがクロージャの強みです。
課題3:設定値管理をクロージャで作る
最後は、設定値(config)を管理する仕組みを作ってみます。 例えば、アプリの設定として次のようなものを考えます。
theme(”light” / “dark”)language(”ja” / “en” など)
これを、クロージャを使って安全に管理してみます。
設定値管理の設計
- 設定値は外から直接書き換えられないようにする。
getConfig()で現在の設定を取得する。setTheme()/setLanguage()で変更する。
クロージャ版設定値管理
// 設定値の型
type Config = {
theme: "light" | "dark";
language: "ja" | "en";
};
// 設定値管理オブジェクトを作る関数
function createConfigManager() {
// 内部状態(外から直接見えない)
let config: Config = {
theme: "light",
language: "ja",
};
function getConfig(): Config {
// オブジェクトをそのまま返すと外から書き換えられるので、
// 必要に応じてコピーを返すことも検討できます
return config;
}
function setTheme(theme: "light" | "dark"): void {
config = {
...config,
theme,
};
}
function setLanguage(language: "ja" | "en"): void {
config = {
...config,
language,
};
}
return {
getConfig,
setTheme,
setLanguage,
};
}
// 設定管理を1つ作る
const configManager = createConfigManager();
console.log("初期設定:", configManager.getConfig());
configManager.setTheme("dark");
configManager.setLanguage("en");
console.log("変更後設定:", configManager.getConfig());
TypeScript深掘りポイント:
configはcreateConfigManagerの中だけで見える変数です。setTheme/setLanguageだけがconfigを変更できます。- 外からは
configに直接触れられないため、「設定値の変更方法」を制御できます。
Day 13 練習テンプレート:3つのクロージャパターン
最後に、今日の3つの課題(カウンター・ID生成・設定値管理)をまとめたテンプレートを載せておきます。
// day13-scope-and-closure.ts
// Day 13:スコープとクロージャ 練習テンプレート
// 1. カウンター
function createCounter() {
let count = 0;
function increment(): void {
count += 1;
}
function decrement(): void {
count -= 1;
}
function getCount(): number {
return count;
}
function reset(): void {
count = 0;
}
return {
increment,
decrement,
getCount,
reset,
};
}
// 2. ID生成器
function createIdGenerator() {
let id = 0;
function nextId(): number {
id += 1;
return id;
}
return {
nextId,
};
}
// 3. 設定値管理
type Config = {
theme: "light" | "dark";
language: "ja" | "en";
};
function createConfigManager() {
let config: Config = {
theme: "light",
language: "ja",
};
function getConfig(): Config {
return config;
}
function setTheme(theme: "light" | "dark"): void {
config = { ...config, theme };
}
function setLanguage(language: "ja" | "en"): void {
config = { ...config, language };
}
return {
getConfig,
setTheme,
setLanguage,
};
}
// テスト
console.log("=== カウンター ===");
const counter = createCounter();
counter.increment();
counter.increment();
console.log(counter.getCount());
counter.reset();
console.log(counter.getCount());
console.log("=== ID生成器 ===");
const idGen = createIdGenerator();
console.log(idGen.nextId());
console.log(idGen.nextId());
console.log("=== 設定値管理 ===");
const cfgMgr = createConfigManager();
console.log(cfgMgr.getConfig());
cfgMgr.setTheme("dark");
cfgMgr.setLanguage("en");
console.log(cfgMgr.getConfig());
TypeScriptDay 13のまとめ
Day 13では、
- グローバルスコープ・関数スコープ・ブロックスコープという「変数がどこから見えるか」のルール
let/constがブロックスコープを持つこと- クロージャが「関数が外側の変数を覚えている状態」であること
- クロージャを使って、内部状態を隠蔽したカウンター・ID生成器・設定値管理を作る方法
を、具体的なコードとともに体験しました。
スコープとクロージャは、最初は少し抽象的に感じるかもしれませんが、こうした小さな仕組みを自分で作ってみると、「あ、こういうときに使うんだ」と感覚がつかめてきます。 ぜひ、初期値を変えられるカウンターや、プレフィックス付きID生成器、より複雑な設定管理など、自分なりのバリエーションを作りながら、スコープとクロージャを手になじませていってください。
