Writer: otsubo 更新日:2024/05/17
こんにちは、デジナーレ福岡オフィスの大坪です。
今回は、JavaScriptにおいて非常に重要な概念のひとつであるスコープについて解説したいと思います。適切にスコープを理解することは、バグの発生を防ぎ、パフォーマンスの向上やメモリ管理の適正化に貢献します。スコープを正しく把握することで、モジュール性の高いコードを書くことができ、コードの保守性と拡張性が格段に向上します。
スコープとは?
スコープとは、定義した変数や関数が参照できる範囲のことを指します。変数やオブジェクトなどが特定のスコープに存在しない場合は参照することが出来ません。また、関数内で宣言された変数はその関数に属するため、他の関数からも参照できません。
そのため、存在しない変数を取得しようとしたとみなされ、参照エラー(RefarenceError)が発生します。
| 
					 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15  | 
						function firstFunction() {     let localVal = "ローカルな変数です"; // firstFunctionスコープに定義された変数     console.log(localVal); // 出力: ローカルな変数です } function secondFunction() {     // secondFunctionからlocalValを参照しようとする     console.log(localVal); // ReferenceError: localVal is not defined }  | 
					
このサンプルのように変数localValはfirstFunction内で宣言されており、そのスコープ外からは参照できません。secondFunctionやグローバルスコープからlocalValを参照しようとすると、ReferenceErrorが発生します。
また、スコープは階層構造を持っているので、子スコープから親スコープの変数にアクセスすることはできますが、その逆である親スコープから子スコープの変数にアクセスすることはできません。
| 
					 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27  | 
						// 親スコープ function parentFunction() {     let parentVal = "親関数で定義された変数";     // 子スコープ     function childFunction() {         let childVal = "子関数で定義された変数";         // 子スコープから親スコープの変数にアクセス         console.log(parentVal);  // 出力: 親関数で定義された変数         console.log(childVal);   // 出力: 子関数で定義された変数     }     childFunction();     // 親スコープから子スコープの変数にアクセスしようとする console.log(childVal) // ReferenceError: childVal is not defined     // プログラムはこの時点でエラーを出して停止します。 } // parentFunctionを呼び出して関数を実行 parentFunction();  | 
					
スコープにはアクセスできる範囲に制約があります。それでは、どのような種類のスコープがあるのでしょうか。まずは、グローバルスコープについて見ていきましょう。
グローバルスコープ
グローバルスコープとは、プログラム全体で定義された変数や関数は、どこからでも参照可能になるスコープです。また、varと使って定義される変数を関数はブロックスコープにならず、グローバルスコープになります。(ブロックスコープについては下記で説明します。)
| 
					 1 2 3 4 5 6 7 8 9 10 11 12  | 
						var globalVal = "グローバル変数"; // グローバルスコープ function myFunction() {     console.log(globalVal); // 出力: グローバル変数 } myFunction();     console.log(globalVal); // 出力:グローバル変数  | 
					
このコードでは、globalValはグローバルスコープに定義されているため、myFunction内および関数外からも参照可能です。
グローバルスコープは便利な反面、乱用するとさまざまな問題を引き起こす可能性があります。
まず、名前の衝突が発生しやすくなります。他の開発者やライブラリから定義された変数と同じ名前を使ってしまうと、意図せずに上書きされてしまう危険があります。
また、グローバル変数はアプリケーション全体から参照可能となるため、どこからでも変更される可能性があります。変数の値が適切に管理されないと、バグの原因になります。
グローバルスコープ以外にローカルスコープに分類される関数スコープとブロックスコープがあります。
ローカルスコープに定義された変数や関数は、そのスコープ外からはアクセスできません。ではそれも見ていきましょう!
関数スコープ
関数スコープは、関数内で宣言された変数の有効範囲を指します。関数外からはアクセスできません。
| 
					 1 2 3 4 5 6 7 8 9 10 11 12  | 
						function myFunction() {   var functionVal = "関数スコープの変数"; // 関数スコープ   console.log(functionVar); // 出力: 関数スコープの変数 } myFunction(); console.log(functionVal); // ReferenceError: functionVar is not defined  | 
					
関数内でのみ有効な変数を作成でき、異なる関数内であれば同じ変数名を使用できます。そして、関数スコープ内の変数は、関数が呼び出されるたびに再作成されます。
| 
					 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17  | 
						function firstFunction() { let functionVal1 = "firstFunction関数スコープの変数"; console.log(functionVal1); // 出力: firstFunction関数スコープの変数 } function secondFunction() { let functionVal2 = "secondFunction関数スコープの変数"; console.log(functionVal2); // 出力: secondFunction関数スコープの変数 console.log(functionVal1); // ReferenceError: functionVal1 is not defined } firstFunction(); secondFunction();  | 
					
functionVal1はfirstFunction関数のスコープ内に限定されており、secondFunction関数のスコープからは参照できないため、ReferenceErrorが発生します。
ブロックスコープ
ブロックスコープは、letとconstで宣言された変数で、特定のブロックである{}内でのみ有効となるスコープのことです。このスコープ内に定義された変数や関数は、そのブロックの外では使用できません。
また、letやconstで宣言された変数は、宣言される前にアクセスするとエラーが発生しますので注意しましょう。
| 
					 1 2 3 4 5 6 7 8 9 10  | 
						if (true) {   let blockVal = "ブロックスコープの変数";   console.log(blockVal); // 出力: "ブロックスコープの変数" }   console.log(blockVal); // ReferenceError: blockVar is not defined  | 
					
一方、varではブロックスコープは適用されず、関数スコープが適用されます。
| 
					 1 2 3 4 5 6 7 8 9 10  | 
						if (true) {   var varVal = "var変数"; // 関数スコープ   console.log(varVal); // 出力:"var変数" }   console.log(varVal); // 出力:"var変数"  | 
					
このように、varとlet/constでは、スコープの扱いが異なります。ES6以降は、できる限りletやconstを使用することが推奨されています。
レキシカルスコープ
| 
					 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15  | 
						function outerFunction() {   let outerVal = "外部の変数";   function innerFunction() {     console.log(outerVal); // 出力: 外部の変数   }   innerFunction(); } outerFunction();  | 
					
この例では、outerFunctionがouterValというローカル変数を作成し、その中でinnerFunctionという内部関数を定義しています。innerFunctionはouterFunctionの中でしか利用できません。しかし、innerFunctionはouterFunctionで宣言されたouterValにアクセスすることができます。これは、入れ子の関数がその外側のスコープで宣言された変数にアクセスできるということを意味します。
スコープチェーン
スコープチェーンは、スコープの階層構造を指し、変数や関数の探索がどのように行われるかを決定します。JavaScriptでは、スコープチェーンを通じて、内側のスコープから外側のスコープへと順に検索を行います。
次のコードを見てみましょう。
| 
					 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25  | 
						let globalVal = "グローバル変数"; function outerFunction() {   let outerVal = "outerFunctionの変数";   function innerFunction() {     let innerVal = "innerFunctionの変数";     function nestedFunction() {       console.log(innerVal); // 出力: innerFunctionの変数       console.log(outerVal); // 出力: outerFunctionの変数       console.log(globalVal); // 出力: グローバル変数     }     nestedFunction();   }   innerFunction(); } outerFunction();  | 
					
このコードでは、関数outerFunctionがouterValという変数を定義し、その中でinnerFunctionを定義しています。さらにinnerFunctionの中でnestedFunctionを定義しています。各関数内で変数をログに出力することで、スコープチェーンの動作を示しています。
スコープチェーンの順序
- まず、現在の関数スコープで変数を探します。
 - もし変数が見つからない場合は、一つ外側のスコープに移動して変数を探します。
 - これを繰り返して、最終的にはグローバルスコープまで検索を行います。
 
具体的なスコープチェーンの動作
- 
innerValの参照:
nestedFunctionのスコープでinnerValを探しますが見つかりません。- 次に親スコープである
innerFunctionのスコープで探します。ここでinnerValが見つかります。 
 - 
outerValの参照:
nestedFunctionおよびinnerFunctionのスコープで見つからないため、さらに外側のouterFunctionのスコープで探します。ここでouterValが見つかります。
 - 
globalValの参照:
nestedFunction、innerFunction、outerFunctionのいずれのスコープでも見つからないため、最終的にグローバルスコープで探します。ここでglobalValが見つかります。
 
クロージャー
クロージャとは、ネストされた関数と、その関数の外側への参照の組み合わせを指します。クロージャを使用すると、子の関数から親である外側の関数スコープに参照できます。
| 
					 1 2 3 4 5 6 7 8 9 10 11 12 13 14  | 
						function createClosure() {   let outerVal = "外側の変数";   return function innerFunction() {     console.log(outerVal); // 出力: 外側の変数   } } const closure = createClosure(); closure();  | 
					
上記の例では、innerFunctionはcreateClosureのスコープ内で宣言された変数outerValを参照できます。これがクロージャーの概念です。クロージャーは関数を作成する度に形成されます。
まとめ
JavaScriptにおけるスコープの理解は、コードの質を大きく左右する重要な概念です。適切にスコープを把握し、変数の有効範囲を意識的に管理することが、バグの防止やパフォーマンス向上に直結します。特にグローバルスコープの乱用は避け、関数スコープやブロックスコープを活用することをおすすめします。
適切なスコープ管理は、保守性と可読性に優れたコードを生み出します。この重要な概念を確実に理解することで、JavaScriptでの実装の質をさらに高められるはずです。