JavaScriptに限らず、プログラミングを学習する上で定義を理解することは中々頭を抱えてしまう問題でもあります。(特に、初学者ではその傾向に陥る人は強いかもしれません。)
今回はJavaScriptの隠蔽について分かりやすく解説記事を書いてみました。
よろしければ、学習の参考に見るなり、この記事をベースに分からないワードについてまとめる手助けになれればなと思います。
JavaScriptエンジンとは
JavaScriptエンジンは、ウェブブラウザの中でJavaScriptコードを読み取り、実行する部分です。このエンジンが、あなたが書いたコードを理解し、それに基づいてウェブページがどう動くかを決めます。
スコープとは何か
スコープは、変数が存在し、アクセス可能な範囲です。プログラミングにおいて、どこで変数にアクセスできるかを決定します。
- グローバルスコープ:プログラム全体でアクセス可能な変数があります。どこからでも使える変数です。
- ローカルスコープ:特定の関数内だけでアクセスできる変数があります。その関数内でのみ使える変数です。
変数の「探し方」
JavaScriptエンジンが変数を「探す」とき、最初にその変数が使われている関数の中を見ます。この「見る範囲」がローカルスコープです。ローカルスコープに変数があれば、それを使います。なければ、もう少し範囲を広げて次は外のスコープ、最終的にはグローバルスコープを見ます。
隠蔽(シャドーイング)とは
ローカルスコープ内でグローバルスコープに存在する変数と同名の変数を宣言すると、その関数内部ではローカル変数が優先されます。これを「隠蔽(シャドーイング)」と呼びます。
なぜローカルスコープが優先されるのか
関数が実行される際、JavaScriptエンジンは変数名を解決するためにまずその関数のローカルスコープをチェックします。ローカルスコープに変数が見つかれば、それを使用します。グローバルスコープまで探しに行く必要がなくなるため、ローカルスコープの変数がグローバルの同名変数を隠蔽します。
箱の中の箱をイメージ
ローカルスコープとグローバルスコープを、箱の中にさらに小さい箱があると考えてみましょう。大きな箱(グローバルスコープ)の中に小さい箱(ローカルスコープ)があります。何かを探すとき、まず小さい箱を開けて中を探し、そこになければ大きな箱を探します。
詳細な例
家(グローバルスコープ)には2つの部屋があります(1つは公共のリビング、もう1つは個人の部屋=ローカルスコープ)。それぞれの部屋に「リモコン」という名前の物があります。
- リビングのリモコン(グローバル変数):家のどこからでも使えます。
- 個人の部屋のリモコン(ローカル変数):その部屋にいるときだけ使えます。
部屋に入ったとき、まずその部屋のリモコンを使います。この行動はJavaScriptエンジンがローカルスコープを先に探すのと似ています。リビングに行くまでの間、個人の部屋のリモコンが使われ、リビングのリモコンは見向きもされません(隠蔽されます)。
具体的なコード例
以下に、変数の隠蔽を説明する具体的なJavaScriptコードを示します。
var color = "blue"; // グローバルスコープの変数
function paint() {
var color = "red"; // ローカルスコープの変数
console.log(color); // この時点ではローカル変数 color が参照されるので "red" が出力される
}
paint(); // 関数を呼び出すと、ローカル変数 color が参照されて "red" が出力される
console.log(color); // 関数の外ではグローバル変数 color が参照されて "blue" が出力される
コードの実行順序
-
グローバル変数の宣言:最初に
var color = "blue";
が実行され、グローバルスコープに変数color
が作成されます。 -
関数の定義:次に、
function paint() { ... }
が定義されますが、この時点では実行されません。 -
関数の呼び出し:
paint();
が呼び出され、関数paint
が実行されます。- 関数
paint
内で、var color = "red";
が宣言されます。この変数はローカルスコープに属します。 -
console.log(color);
が実行され、この時点でローカル変数color
が参照されるため、"red"が出力されます。
- 関数
-
グローバル変数の参照:関数の外で
console.log(color);
が実行されます。この時点でグローバル変数color
が参照されるため、"blue"が出力されます。
変数の解決(名前解決)の仕組み
JavaScriptエンジンは変数名を解決するために、次のような順序でスコープをチェックします:
- 現在のスコープ(ローカルスコープ)
- そのスコープの外側のスコープ(親スコープ)
- 最外のスコープ(グローバルスコープ)
この仕組みを「スコープチェーン」と呼びます。
スコープチェーンの具体的な例
以下の例を使って、どのようにスコープチェーンが働くかを説明します:
var globalVar = "I am global";
function outerFunction() {
var outerVar = "I am outer";
function innerFunction() {
var innerVar = "I am inner";
// この中で各変数がどのように解決されるか見てみましょう
console.log(innerVar); // "I am inner"(ローカルスコープで見つかる)
console.log(outerVar); // "I am outer"(親スコープで見つかる)
console.log(globalVar); // "I am global"(グローバルスコープで見つかる)
}
innerFunction();
}
outerFunction();
詳細な解説
-
グローバルスコープ:
-
var globalVar = "I am global";
がグローバルスコープで定義されます。
-
-
outerFunction
のスコープ:-
var outerVar = "I am outer";
がouterFunction
のローカルスコープで定義されます。 - この関数内に、さらに
innerFunction
が定義されています。
-
-
innerFunction
のスコープ:-
var innerVar = "I am inner";
がinnerFunction
のローカルスコープで定義されます。 -
innerFunction
内で変数が参照されるとき、JavaScriptエンジンはまずその変数名をinnerFunction
のローカルスコープ内で探します。 -
innerVar
はinnerFunction
のローカルスコープで見つかります。 -
outerVar
はinnerFunction
のローカルスコープには存在しないため、一つ外側のスコープ(outerFunction
のローカルスコープ)で探します。ここで見つかります。 -
globalVar
はどちらのローカルスコープにも存在しないため、最外のグローバルスコープで探します。ここで見つかります。
-
具体的なステップ
-
console.log(innerVar);
:-
innerFunction
のローカルスコープ内でinnerVar
を探します。ここで見つかるため、"I am inner"が出力されます。
-
-
console.log(outerVar);
:-
innerFunction
のローカルスコープ内でouterVar
を探しますが見つかりません。次に外側のスコープ(outerFunction
のローカルスコープ)で探し、ここで見つかるため、"I am outer"が出力されます。
-
-
console.log(globalVar);
:-
innerFunction
のローカルスコープとouterFunction
のローカルスコープの両方でglobalVar
を探しますが見つかりません。最終的にグローバルスコープで探し、ここで見つかるため、"I am global"が出力されます。
-
まとめ
このように、JavaScriptエンジンは変数名を解決する際に、まずその変数が使用されている関数のローカルスコープを優先してチェックします。それが見つからない場合に限り、外側のスコープ(親スコープ)をチェックし、最終的にグローバルスコープをチェックします。この仕組みは、スコープチェーンと呼ばれ、変数の名前解決の基本的なメカニズムです。この方法により、関数内で同名の変数が独立して使用でき、コードの予測可能性と管理のしやすさが向上します。