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での実装の質をさらに高められるはずです。