はじめてのTypeScriptプログラミング入門 30日コース | ObjectとFunction - Day 13:スコープ

30日で身につけるTypeScript
スポンサーリンク
スポンサーリンク
  1. Day 13:スコープとクロージャで「賢いカウンター」を作る
  2. スコープとは:変数が「どこから見えるか」のルール
    1. グローバルスコープ
    2. 関数スコープ
    3. ブロックスコープ(let / const)
  3. クロージャの基本:関数が「外の変数」を覚えている
    1. クロージャとは何か
  4. 実践:クロージャでカウンターを作る
    1. まずは「素朴な」グローバルカウンター
  5. クロージャで「隠れた状態」を持つカウンターを作る
    1. カウンターを生成する関数を作る
  6. 複数のカウンターを独立して持つ
  7. let / const とスコープの関係をもう一度整理する
  8. Day 13 練習テンプレート:スコープとクロージャを意識したカウンター
  9. Day 13のまとめ
  10. Day 13:スコープとクロージャで「小さな仕組み」を賢くする
  11. スコープの基本:変数が「どこから見えるか」のルール
    1. グローバルスコープ
    2. 関数スコープ
    3. ブロックスコープ(let / const)
  12. クロージャの基本:関数が「外の変数」を覚えている
    1. クロージャとは
  13. 課題1:クロージャでカウンターを作る
    1. グローバル変数版との違いを意識する
    2. クロージャ版カウンター
  14. 課題2:ID生成器をクロージャで作る
    1. グローバル変数版
    2. クロージャ版ID生成器
  15. 課題3:設定値管理をクロージャで作る
    1. 設定値管理の設計
    2. クロージャ版設定値管理
  16. Day 13 練習テンプレート:3つのクロージャパターン
  17. 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)

letconst は、ブロックスコープを持ちます。 ブロックとは、{ ... } で囲まれた範囲のことです。

if (true) {
  let x = 10;
  const y = 20;
  console.log("ブロック内:", x, y); // 10 20
}

// ブロックの外からは見えない
// console.log(x); // エラー
// console.log(y); // エラー
TypeScript

ポイント:

  • letconst で宣言した変数は、そのブロックの外からは見えません。
  • 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 が作られます。
  • counterAcounterB は、それぞれ別の count を持っています。
  • グローバル変数でカウンターを作ると、こうした「独立した複数の状態」を持つのが難しくなりますが、クロージャなら自然にできます。

let / const とスコープの関係をもう一度整理する

カウンターの例を通して、let / const とスコープの関係をもう一度確認しておきます。

function createCounter() {
  // 関数スコープ内の変数(ブロックスコープでもある)
  let count = 0;

  function increment() {
    // 同じスコープ内の count にアクセス
    count += 1;
  }

  return {
    increment,
  };
}
TypeScript
  • countcreateCounter 関数の中だけで見える変数です。
  • 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
TypeScript

Day 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)

letconst は、ブロックスコープを持ちます。 ブロックとは、{ ... } で囲まれた範囲のことです。

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

重要ポイント:

  • greetcreateGreeter の中で定義されていますが、外側の 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

深掘りポイント:

  • countcreateCounter の中だけで見える変数です。
  • 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 が作られます。
  • idGenAidGenB は、それぞれ独立した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

深掘りポイント:

  • configcreateConfigManager の中だけで見える変数です。
  • 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());
TypeScript

Day 13のまとめ

Day 13では、

  • グローバルスコープ・関数スコープ・ブロックスコープという「変数がどこから見えるか」のルール
  • let / const がブロックスコープを持つこと
  • クロージャが「関数が外側の変数を覚えている状態」であること
  • クロージャを使って、内部状態を隠蔽したカウンター・ID生成器・設定値管理を作る方法

を、具体的なコードとともに体験しました。

スコープとクロージャは、最初は少し抽象的に感じるかもしれませんが、こうした小さな仕組みを自分で作ってみると、「あ、こういうときに使うんだ」と感覚がつかめてきます。 ぜひ、初期値を変えられるカウンターや、プレフィックス付きID生成器、より複雑な設定管理など、自分なりのバリエーションを作りながら、スコープとクロージャを手になじませていってください。

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