ブログblog

JavaScriptのスコープについて

Writer: otsubo 更新日:2024/05/17

こんにちは、デジナーレ福岡オフィスの大坪です。

今回は、JavaScriptにおいて非常に重要な概念のひとつであるスコープについて解説したいと思います。適切にスコープを理解することは、バグの発生を防ぎ、パフォーマンスの向上やメモリ管理の適正化に貢献します。スコープを正しく把握することで、モジュール性の高いコードを書くことができ、コードの保守性と拡張性が格段に向上します。

 

スコープとは?

スコープとは、定義した変数や関数が参照できる範囲のことを指します。変数やオブジェクトなどが特定のスコープに存在しない場合は参照することが出来ません。また、関数内で宣言された変数はその関数に属するため、他の関数からも参照できません。
そのため、存在しない変数を取得しようとしたとみなされ、参照エラー(RefarenceError)が発生します。

このサンプルのように変数localValfirstFunction内で宣言されており、そのスコープ外からは参照できません。secondFunctionやグローバルスコープからlocalValを参照しようとすると、ReferenceErrorが発生します。

また、スコープは階層構造を持っているので、子スコープから親スコープの変数にアクセスすることはできますが、その逆である親スコープから子スコープの変数にアクセスすることはできません。

スコープにはアクセスできる範囲に制約があります。それでは、どのような種類のスコープがあるのでしょうか。まずは、グローバルスコープについて見ていきましょう。

 

グローバルスコープ

グローバルスコープとは、プログラム全体で定義された変数や関数は、どこからでも参照可能になるスコープです。また、varと使って定義される変数を関数はブロックスコープにならず、グローバルスコープになります。(ブロックスコープについては下記で説明します。)

このコードでは、globalValはグローバルスコープに定義されているため、myFunction内および関数外からも参照可能です。

グローバルスコープは便利な反面、乱用するとさまざまな問題を引き起こす可能性があります。
まず、名前の衝突が発生しやすくなります。他の開発者やライブラリから定義された変数と同じ名前を使ってしまうと、意図せずに上書きされてしまう危険があります。
また、グローバル変数はアプリケーション全体から参照可能となるため、どこからでも変更される可能性があります。変数の値が適切に管理されないと、バグの原因になります。

グローバルスコープ以外にローカルスコープに分類される関数スコープとブロックスコープがあります。
ローカルスコープに定義された変数や関数は、そのスコープ外からはアクセスできません。ではそれも見ていきましょう!

 

関数スコープ

関数スコープは、関数内で宣言された変数の有効範囲を指します。関数外からはアクセスできません。

関数内でのみ有効な変数を作成でき、異なる関数内であれば同じ変数名を使用できます。そして、関数スコープ内の変数は、関数が呼び出されるたびに再作成されます。

しかし、異なる関数スコープに配置された変数や関数を使うことはできません。
上記の例では、functionVal1firstFunction関数のスコープ内に限定されており、secondFunction関数のスコープからは参照できないため、ReferenceErrorが発生します。
関数スコープはグローバルスコープとは違い、関数の外で値が更新されることがなく、保守性が上がります。

 

ブロックスコープ

ブロックスコープは、letconstで宣言された変数で、特定のブロックである{}内でのみ有効となるスコープのことです。このスコープ内に定義された変数や関数は、そのブロックの外では使用できません。
また、letconstで宣言された変数は、宣言される前にアクセスするとエラーが発生しますので注意しましょう。

一方、varではブロックスコープは適用されず、関数スコープが適用されます。

このように、varlet/constでは、スコープの扱いが異なります。ES6以降は、できる限りletconstを使用することが推奨されています。

 

レキシカルスコープ

レキシカルスコープは、関数がどこで定義されたかによって、その関数が参照できる変数が決まるというものです。レキシカルスコープは、実行中のコードが属しているスコープの外側のスコープを指します。

この例では、outerFunctionouterValというローカル変数を作成し、その中でinnerFunctionという内部関数を定義しています。innerFunctionouterFunctionの中でしか利用できません。しかし、innerFunctionouterFunctionで宣言されたouterValにアクセスすることができます。これは、入れ子の関数がその外側のスコープで宣言された変数にアクセスできるということを意味します。

 

スコープチェーン

スコープチェーンは、スコープの階層構造を指し、変数や関数の探索がどのように行われるかを決定します。JavaScriptでは、スコープチェーンを通じて、内側のスコープから外側のスコープへと順に検索を行います。

次のコードを見てみましょう。

このコードでは、関数outerFunctionouterValという変数を定義し、その中でinnerFunctionを定義しています。さらにinnerFunctionの中でnestedFunctionを定義しています。各関数内で変数をログに出力することで、スコープチェーンの動作を示しています。

スコープチェーンの順序

  1. まず、現在の関数スコープで変数を探します。
  2. もし変数が見つからない場合は、一つ外側のスコープに移動して変数を探します。
  3. これを繰り返して、最終的にはグローバルスコープまで検索を行います。

 

具体的なスコープチェーンの動作

  • innerValの参照:

    • nestedFunctionのスコープでinnerValを探しますが見つかりません。
    • 次に親スコープであるinnerFunctionのスコープで探します。ここでinnerValが見つかります。
  • outerValの参照:

    • nestedFunctionおよびinnerFunctionのスコープで見つからないため、さらに外側のouterFunctionのスコープで探します。ここでouterValが見つかります。
  • globalValの参照:

    • nestedFunctioninnerFunctionouterFunctionのいずれのスコープでも見つからないため、最終的にグローバルスコープで探します。ここでglobalValが見つかります。

 

クロージャー

クロージャとは、ネストされた関数と、その関数の外側への参照の組み合わせを指します。クロージャを使用すると、子の関数から親である外側の関数スコープに参照できます。

上記の例では、innerFunctioncreateClosureのスコープ内で宣言された変数outerValを参照できます。これがクロージャーの概念です。クロージャーは関数を作成する度に形成されます。

 

 

まとめ

JavaScriptにおけるスコープの理解は、コードの質を大きく左右する重要な概念です。適切にスコープを把握し、変数の有効範囲を意識的に管理することが、バグの防止やパフォーマンス向上に直結します。特にグローバルスコープの乱用は避け、関数スコープやブロックスコープを活用することをおすすめします。

適切なスコープ管理は、保守性と可読性に優れたコードを生み出します。この重要な概念を確実に理解することで、JavaScriptでの実装の質をさらに高められるはずです。