【TypeScript】変数スコープを完全理解!実例10選で徹底解説

TypeScriptでの変数スコープの解説とサンプルコードのイラストTypeScript
この記事は約20分で読めます。

 

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

基本的な知識があればカスタムコードを使って機能追加、目的を達成できるように作ってあります。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を凌駕する現役のプログラマチームによって監修されています。

サイト内のコードを共有する場合は、参照元として引用して下さいますと幸いです

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

はじめに

TypeScriptはJavaScriptのスーパーセットとして、静的な型システムを持つことで、より安全で効果的なプログラムの記述を可能にします。

その中でも、変数のスコープは非常に重要な概念となっています。

この記事では、初心者から上級者まで、TypeScriptの変数スコープの理解を深めるための具体的な実例を交えて、変数スコープの重要性や使い方、応用方法について徹底的に解説します。

変数の扱いが難しいあなたでも、この記事を通じて、TypeScriptの変数スコープをしっかりとマスターする力が身につくことでしょう。

それでは、TypeScriptの変数スコープの魅力とその活用方法を一緒に学びましょう!

●TypeScriptの変数スコープとは

変数スコープは、変数がアクセス可能な範囲を指定するものです。

言い換えれば、変数がどこから参照・操作できるのか、その範囲を定義するものと言えます。

○基本的なスコープの種類

  1. グローバルスコープ:プログラムのどこからでもアクセス可能な変数が存在する範囲です。
  2. 関数スコープ:ある関数内でのみアクセス可能な変数が存在する範囲です。
  3. ブロックスコープ:{}(中括弧)で囲まれた領域内でのみアクセス可能な変数が存在する範囲です。

これらのスコープに関して、詳しくは後のセクションでサンプルコードを交えて説明します。

●変数スコープの使い方

TypeScriptを用いた開発では、変数スコープの使い方を理解することが不可欠です。

実際にコードに落とし込む上での最初の大きなステップとして、varlet のスコープの差異を掴むことが挙げられます。

この二つのキーワードによって同一の変数名が同じスコープで意図しない動作をするかもしれず、それを避けるためにもそれぞれの挙動の違いを明確に把握する必要があります。

ここでは実際にコードを使って、varlet のスコープに関する根本的な違いを具体化し、この重要点について掘り下げていきましょう。

○サンプルコード1:varとletの違い

このコードでは、varletのキーワードを使用して変数を宣言する際のスコープの違いを表します。

この例では、varが関数スコープを持ち、letがブロックスコープを持つことを表しています。

function varVsLet() {
    if (true) {
        var varVariable = "I am var";
        let letVariable = "I am let";
    }

    console.log(varVariable); // "I am var"
    // console.log(letVariable); // Error: letVariable is not defined
}

上記のコードでは、varVariableは関数全体でアクセス可能であるため、エラーが発生せずに出力されます。

一方、letVariableはブロック内でのみ有効なため、関数の外からは参照できません。

実際に上のコードを実行すると、"I am var"という文字列が出力され、letVariableに関する部分でエラーが発生することがわかります。

○サンプルコード2:ブロックスコープの理解

このコードでは、ブロックスコープがどのように機能するのかを表しています。

この例では、letを使用してブロック内で変数を宣言しています。

function blockScopeUnderstanding() {
    let outsideVariable = "Outside the block";

    if (true) {
        let insideVariable = "Inside the block";
        console.log(outsideVariable); // "Outside the block"
        console.log(insideVariable); // "Inside the block"
    }

    console.log(outsideVariable); // "Outside the block"
    // console.log(insideVariable); // Error: insideVariable is not defined
}

insideVariableはブロック内でのみアクセス可能であり、ブロックの外では参照できません。

そのため、最後のconsole.log(insideVariable);の部分でエラーが発生します。

コードを実行すると、"Outside the block"という文字列が2回、"Inside the block"という文字列が1回出力されることがわかります。

また、最後の行でエラーが発生することも確認できます。

○サンプルコード3:関数スコープの特性

TypeScriptにおいて、関数スコープは非常に重要な役割を果たしています。

これは、特定の変数や定数が関数内部だけで利用可能となる範囲を指します。

ここでは、関数スコープの主な特性とその使用方法を、具体的なサンプルコードを交えて詳しく解説していきます。

このコードでは、関数スコープ内で宣言された変数の振る舞いを表しています。

この例では、関数内で宣言された変数が外部からアクセスできないことを表しています。

function testFunctionScope() {
    let innerVariable = "関数スコープ内の変数です";
    console.log(innerVariable); // 関数スコープ内の変数です
}

testFunctionScope();
// console.log(innerVariable); この行はエラーになります。innerVariableは関数の外からアクセスできないためです。

上記のコードを実行すると、関数testFunctionScope内で宣言された変数innerVariableは、関数の外からは参照できないことが確認できます。

コメントアウトされているconsole.log(innerVariable);を実行しようとすると、エラーが発生します。

これは、innerVariableが関数スコープ内でのみ有効であるため、関数の外からはアクセスできないという特性を示しています。

このような特性は、変数の生存期間や可視性を制御する際に非常に役立ちます。

例えば、関数内でのみ使用する一時的な変数や、外部からのアクセスを避けたいセキュアな情報を保存する際などに有効です。

○サンプルコード4:グローバルスコープの活用

TypeScriptにおいて、スコープは変数が利用可能な範囲を表すものです。

その中でもグローバルスコープは、全てのローカルスコープからアクセス可能なスコープです。

グローバルスコープにある変数は、どこからでも参照・変更ができます。

このコードではグローバルスコープに変数を定義して、異なる関数からその変数を参照する例を表しています。

この例ではglobalVarという変数をグローバルスコープに定義して、それをdisplayVar関数で表示しています。

// グローバルスコープに変数を定義
let globalVar = "TypeScriptのグローバル変数";

// グローバルスコープの変数を表示する関数
function displayVar() {
    // グローバルスコープの変数にアクセス
    console.log(globalVar);
}

displayVar(); // TypeScriptのグローバル変数と表示される

このコードを実行すると、”TypeScriptのグローバル変数”という文字列がコンソールに表示されます。

これは、displayVar関数内からグローバルスコープの変数globalVarにアクセスして取得した値を表示しているためです。

しかし、グローバルスコープに変数を無闇に置いてしまうと、意図しない変数の上書きや予期せぬ挙動の原因となる可能性があります。

そのため、グローバルスコープの変数は極力少なくし、必要な場合のみ使用することが推奨されます。

次に、グローバルスコープの変数をローカルスコープで上書きする例を見てみましょう。

let globalVar = "元のグローバル変数";

function overrideVar() {
    let globalVar = "上書きされたローカル変数";
    console.log(globalVar); // 上書きされたローカル変数と表示される
}

overrideVar();

console.log(globalVar); // 元のグローバル変数と表示される

上記のコードでは、関数overrideVar内でglobalVarという名前のローカル変数を定義しています。

このローカル変数は関数内でのみ有効で、関数外のグローバル変数globalVarとは異なるものとして扱われます。

そのため、関数内でのconsole.logはローカル変数の値を、関数外でのconsole.logはグローバル変数の値をそれぞれ表示します。

このように、グローバルスコープとローカルスコープの変数が名前が同じ場合でも、それぞれ異なるスコープで定義されているため区別されます。

しかし、変数の名前が重複しているとコードの読み手が混乱する可能性があるため、異なるスコープで同じ名前の変数を避けることが望ましいです。

●変数スコープの応用例

変数のスコープという概念は、TypeScriptをはじめとした多くのプログラミング言語で共通しています。

しかし、TypeScriptの強力な型システムやモダンな文法により、変数スコープを使ったさまざまな応用例やテクニックが可能となっています。

ここでは、これらの応用例をいくつかのサンプルコードを交えて詳しく紹介します。

○サンプルコード5:クロージャの利用方法

クロージャは、関数が生成された環境を「キャッチ」して、その環境外からも参照可能にする特性を指します。

下記のコードでは、createCounter関数を使って、外部から直接触れないプライベートな変数countを持つカウンタオブジェクトを作成しています。

function createCounter() {
  let count = 0; // この変数は外部から直接触れない

  return {
    increment: function() {
      count++;
      return count;
    },
    decrement: function() {
      count--;
      return count;
    },
    getCount: function() {
      return count;
    }
  };
}

const counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.decrement()); // 0
console.log(counter.getCount());  // 0

このコードではcreateCounter関数内部のcount変数を使ってカウントを増減するメソッドを提供しています。

この例ではcount変数を外部から直接操作することなく、カウントの管理を行っています。

○サンプルコード6:モジュールスコープ

TypeScriptにはモジュールという概念があります。

それぞれのモジュールは、独自のスコープを持ちます。

モジュールスコープを利用したシンプルな例を紹介します。

// math.ts モジュール
export const pi = 3.141592;

export function add(a: number, b: number): number {
  return a + b;
}

上記のmath.tsモジュールでは、定数piと関数addを外部に公開しています。

このモジュール内の変数や関数は、明示的にexportしない限り、他のモジュールから参照することができません。

○サンプルコード7:ネストされたスコープの扱い方

TypeScriptの関数やブロックの中にはさらに関数やブロックを記述することで、スコープをネストすることができます。

ネストされたスコープを持つサンプルコードを紹介します。

function outerFunction() {
  let outerVar = '外側の関数の変数';

  function innerFunction() {
    let innerVar = '内側の関数の変数';
    console.log(outerVar); // "外側の関数の変数"
    console.log(innerVar); // "内側の関数の変数"
  }

  innerFunction();
}

outerFunction();

上記のコードでは、innerFunctionの内部からouterFunctionのスコープに存在するouterVarを参照することができます。

しかし、反対にouterFunctionからinnerVarを参照することはできません。

○サンプルコード8:スコープチェーンの実際

スコープチェーンとは、現在のスコープから順番に外側のスコープへと変数や関数の参照を探していく仕組みを指します。

下記のコードはスコープチェーンを活用した例です。

let globalVar = 'グローバル変数';

function showScopeChain() {
  let localVar = 'ローカル変数';

  function innerFunction() {
    console.log(globalVar); // "グローバル変数"
    console.log(localVar);  // "ローカル変数"
  }

  innerFunction();
}

showScopeChain();

このコードでは、innerFunctionの中からglobalVarlocalVarの両方を参照しています。

innerFunctionはまず現在のスコープを検索し、そこに変数がない場合は外側のスコープへと探しに行きます。

このようにして、必要な変数を見つけ出すことができるのです。

○サンプルコード9:ダイナミックスコープの例

TypeScriptは基本的にはレキシカルスコープ(またはスタティックスコープ)を採用しています。

しかし、JavaScriptやTypeScriptの特定の挙動を模倣することで、ダイナミックスコープのような挙動を再現することが可能です。

ダイナミックスコープとは、変数を参照する際に、その変数が呼び出された場所ではなく、現在の実行コンテキストに基づいて変数を解決する方式を指します。

これは、特定のプログラムの動的な実行コンテキストに依存するため、予期しない挙動を引き起こす可能性があります。

ダイナミックスコープのような挙動を再現するサンプルコードを紹介します。

let name = 'Taro';

function showName() {
    console.log(name);
}

function executeFunction(func: Function) {
    let name = 'Hanako';
    func();
}

executeFunction(showName);

このコードでは、showName関数をexecuteFunction関数で呼び出しています。

showName関数の定義時にはname変数はTaroという値を持っていますが、executeFunction関数内で再定義してHanakoとしています。

上記のコードを実行すると、Hanakoが出力されることを確認できます。

これは、関数showNameが呼び出された際の実行コンテキスト内の変数nameを参照しているためです。

この挙動は、レキシカルスコープの基本的な特性からは外れる部分があり、ダイナミックスコープの特性を再現していると言えます。

ただし、これはTypeScriptやJavaScriptがダイナミックスコープをサポートしているわけではなく、関数の引数や変数の再定義など、特定のパターンを利用してこのような挙動を実現しているだけです。

このような特性を理解しておくことで、意図しない変数の参照や再定義によるバグを防ぐことができます。

特に大規模なプロジェクトや複数人での開発を行う際には、このような挙動を意識してコードを書くことが求められます。

○サンプルコード10:スコープの制限と拡張

TypeScriptはJavaScriptのスーパーセットであり、多くの新しい機能と型の安全性を提供します。

その中の一つとして、変数のスコープを制限または拡張するための方法も提供されています。

ここでは、TypeScriptでのスコープの制限と拡張の方法について詳しく解説していきます。

□スコープの制限

スコープの制限とは、変数や関数がアクセス可能な範囲を狭めることを指します。

この技術は、特定のコードブロック内でのみ変数や関数を使用したい場合に役立ちます。

このコードでは、innerFunction内で定義されたinnerVariableという変数があります。

この変数は、innerFunctionのスコープ内でのみアクセス可能であり、外部からはアクセスできません。

function outerFunction() {
    let outerVariable = "外部変数";

    function innerFunction() {
        let innerVariable = "内部変数";
        console.log(innerVariable); // "内部変数" と出力される
    }

    innerFunction();
    // console.log(innerVariable); // エラー: innerVariableはここではアクセスできない
}

□スコープの拡張

スコープの拡張とは、変数や関数がアクセス可能な範囲を広げることを指します。

TypeScriptでは、namespaceを使用してスコープを拡張することができます。

このコードでは、MyNamespaceという名前空間内にmyFunctionという関数を定義しています。

この関数は、名前空間を通じて外部からもアクセス可能です。

namespace MyNamespace {
    export function myFunction() {
        console.log("名前空間内の関数");
    }
}

MyNamespace.myFunction(); // "名前空間内の関数" と出力される

以上のように、TypeScriptではスコープの制限と拡張を柔軟に行うことができます。

これにより、コードの保守性や再利用性を向上させることができます。

これらのコードの実行結果について見ていきましょう。

最初のコードの場合、innerFunction内で定義されたinnerVariableは、その関数のスコープ内でのみアクセス可能であり、関数外部からはアクセスすることができません。

そのため、コメントアウトされたconsole.log(innerVariable);を実行するとエラーが発生します。

一方、二番目のコードの場合、MyNamespaceという名前空間を通じて、その名前空間内に定義された関数や変数にアクセスすることができます。

このため、MyNamespace.myFunction();を実行すると、正常に関数が呼び出され、”名前空間内の関数”というメッセージが出力されます。

●注意点と対処法

TypeScriptで変数のスコープを効果的に使うためには、その特性や挙動を理解するだけでなく、様々な注意点も覚えておくことが重要です。

これから、TypeScriptの変数スコープを使用する上での注意点と、それに対する対処法を紹介します。

○変数のホイスティングについて

まず初めに「ホイスティング」とは何かというと、JavaScriptやTypeScriptにおいて変数や関数の宣言がそのスコープの先頭に移動するという挙動のことを指します。

このコードでは変数のホイスティングを表しています。

この例ではvarキーワードを使って変数を宣言していますが、実際の動作は初見の人には意外かもしれません。

function demoFunction() {
    console.log(value); // undefined
    var value = "TypeScript";
    console.log(value); // TypeScript
}
demoFunction();

このコードを一見すると、最初のconsole.logでエラーが発生するのではないかと思うかもしれません。

しかし、実際にはvalueはホイスティングにより関数の先頭で宣言されているため、エラーは発生しません。

ただし、初めてのログ出力時点では変数に値は代入されていないため、undefinedという結果になります。

対処法として、変数を使用する前に必ず宣言・初期化を行うことが推奨されます。

また、TypeScriptではletconstキーワードを使用することで、ホイスティングの問題を回避することができます。

○ブロックスコープのトリッキーな部分

ブロックスコープに関しても、いくつかの注意点が存在します。

ブロックスコープは、letconstを使用した場合のスコープの範囲を指します。

このコードでは、ブロック内の変数スコープの挙動を表しています。

この例ではif文のブロック内で変数を宣言していますが、その変数はブロックの外からは参照できません。

if (true) {
    let blockScopeValue = "Inside block";
}
console.log(blockScopeValue); // エラー: blockScopeValue is not defined

上記のコードを実行すると、blockScopeValueはブロックの外からは参照できず、エラーが発生します。

ブロックスコープの変数は、そのブロックの中でのみアクセス可能です。

対処法として、変数のスコープを適切に管理し、変数の生存期間やアクセス可能範囲を明確にすることが大切です。

また、グローバルスコープに変数を置くのは避け、なるべく狭いスコープで変数を管理することをおすすめします。

●カスタマイズ方法

TypeScriptの変数スコープは、基本的な使い方や応用例を理解するだけでなく、独自のニーズに合わせてカスタマイズする方法も知っておくことが重要です。

ここでは、独自のスコープを効果的に作成するためのアドバイスを交えながら、TypeScriptでのスコープのカスタマイズ方法を深掘りします。

○独自のスコープ作成のアドバイス

実際にプロジェクトでTypeScriptを利用する際、既存のスコープだけではなく、独自のスコープを設定したい場合が出てきます。

その際のポイントやアドバイスを紹介します。

□名前空間を利用する

TypeScriptには、名前空間(またはモジュール)という概念があり、これを利用することで、変数や関数などのメンバーをグループ化してスコープを形成することができます。

このコードでは、MyNamespaceという名前空間を定義し、その中にmyFunctionという関数を作成しています。

この例では、名前空間を用いて関数をグループ化しています。

namespace MyNamespace {
  export function myFunction() {
    console.log("この関数はMyNamespace内に存在します。");
  }
}

// 名前空間内の関数を呼び出す
MyNamespace.myFunction();

この方法を使用することで、特定の機能や目的に合わせたスコープを形成することができ、コードの整理や管理がしやすくなります。

□クロージャを活用する

JavaScriptおよびTypeScriptにおいて、クロージャは非常に有効な手段であり、関数内部に変数を閉じ込めることで独自のスコープを作成することができます。

このコードでは、createCounterという関数が外部の変数countにアクセスしています。

この例では、クロージャを利用してcount変数を外部から隠蔽し、特定の操作のみを許可しています。

function createCounter() {
  let count = 0;
  return {
    increment: function() {
      count++;
      console.log(count);
    }
  };
}

const counter = createCounter();
counter.increment();  // 1
counter.increment();  // 2

上記のサンプルコードを実行すると、counter.increment()を呼び出すたびに、カウントアップされた数字が出力されます。

カスタマイズのポイントとして、スコープの設計や変数のアクセス範囲を事前にしっかりと計画することが挙げられます。

また、過度なカスタマイズはコードの可読性を損なう可能性があるため、バランスを考慮しながら適切な方法を選択することが必要です。

まとめ

TypeScriptの変数スコープに関する概念と利用法を十分に理解することは、より効果的なコードを書くための鍵です。

この記事で、初心者から上級者までの読者が変数スコープの概念を明確に把握できるよう、多くの実例と共に詳細に解説しました。

この記事を読むことで、TypeScriptの変数スコープに関する知識が深まり、より質の高いコードを書く力が身についたことでしょう。

日々のコーディングの中で、この記事で学んだ知識を活かして、効果的かつ安全に変数を扱うスキルを磨いてください。