はじめに
JavaScriptのスコープについて、あらためて勉強する機会があったので、まとめてみました。
プログラミングにおけるスコープ
はじめに、そもそもスコープとは何なのかを理解する必要があります。プログラミングにおけるスコープとは、変数や関数の有効範囲のことを言います。ある変数や関数が見える範囲と言い換えてもいいかもしれません。
プログラミングでのスコープとは、ある変数や関数が特定の名前で参照される範囲のこと。ある範囲の外に置いた変数等は、通常、その名前だけでは参照できない。このときこれらの変数はスコープ外である、「見えない」といわれる。
スコープ - Wikipedia
JavaScriptのスコープ
JavaScriptのスコープは、原則、以下の2つしかありません。
- グローバルスコープ
- ローカルスコープ
グローバルスコープ
グローバルスコープとは、プログラム中のどこからでも有効な範囲です。scriptタグ内に定義した変数はグローバル変数となり、関数はグローバル関数となります。
以下の例では、関数Aの中からグローバル変数xが参照できることを示しています。
<script type="text/javascript"> var x = 1; function A() { x = 0; // グローバル変数 return x; } alert(A()); // >>0 alert(x); // >>0 </script>
ローカルスコープ
関数の中でのみ有効な範囲です。関数内でvarをつけて宣言した変数は、ローカル変数となります。以下の例では、関数内の変数xとグローバル変数xは、別物であることを示しています。
var x = 1; function A() { var x = 0; // ローカル変数 return x; } alert(A()); // >>0 alert(x); // >>1
ブロックスコープ
C言語やJavaには、ブロックスコープというスコープがあります。if文やfor文といったブロック内に限定されたスコープですが、JavaScriptにはこのブロックスコープがありません。
以下のコードは、1〜9までを加算するプログラムですが、forブロック内で宣言された変数iは、forブロックが終了しても残り続けてしまいます。
var sum = 0; for(var i=0; i<10; i++) { sum += i; } alert(sum); // >>45 alert(i); // >>10
C言語やJavaプログラマにとって、このブロックスコープがないというのは違和感を覚えるかもしれません。しかしながら、JavaScriptでもブロックスコープを実現する方法がないわけではありません。
letキーワードを使ってブロックスコープを実現する
JavaScript1.7からは、letキーワードを用いることで、ブロックスコープを実現することができます。ただし、この方法はFirefox2.0以上でしか利用できないため、あまり現実的ではありません。
これまで JavaScript には大まかにいえば大域スコープと関数内スコープしか存在しなかったが、ブロックスコープを実現すべく let 文、let 式、let 宣言が導入された (Bug 336378) 。
JavaScript 1.7 の新機能: Days on the Moon
<script type="application/javascript;version=1.7"/> var sum = 0; for(let i=0; i<10; i++) { sum += i; } alert(sum); // >>45 alert(i); // >>エラー: i is not defined </script>
無名関数を使ってブロックスコープを実現する
関数内はローカルスコープになるという仕組みを利用し、関数を定義・即実行することで、擬似的にブロックスコープを実現することができます。
var sum = 0; (function () { for(var i=0; i<10; i++) { sum += i; } })(); alert(sum); // >>45 alert(i); // >>エラー: i is not defined
With文を使ってブロックスコープを実現する
With文を使用しても擬似的なブロックスコープを実現することができます。
with 文とオブジェクトリテラルを使えばブロックスコープを実現できることを Bug 343765 に添えられたテストケースで知った。
JavaScript でブロックスコープを実現する: Days on the Moon
var sum = 0; with ({ i: 0 }) { for (i = 0; i < 10; i++) { sum += i; } } alert(sum); // >>45 alert(i); // >>エラー: i is not defined
変数を管理する変数オブジェクト
JavaScriptでは変数を宣言すると、変数管理用のハッシュテーブル(変数オブジェクト)に値が追加されます。この変数オブジェクトはグローバルスコープとローカルスコープとでは異なります。
変数を宣言すると、変数名をキー、undefinedを値とするエントリがハッシュテーブル内に作られます。変数に値を代入すると、ハッシュテーブル内の値が書き換わります。変数の参照は、変数名をキーとして値を取り出す事と等価です。
JavaScriptクロージャを完全理解!スコープチェインを知る(前編) - page2 - builder by ZDNet Japan
グローバルスコープにおける変数オブジェクト
グローバルスコープでは、グローバルオブジェクト(window)が変数オブジェクトとなります。グローバル変数は、windowオブジェクトのプロパティとして登録されます。
var x; var y = 1; alert(window.x); // >>undefined alert(window['x']); // >>undefined alert(window.y); // >>1 alert(window['y']); // >>1
ローカルスコープにおける変数オブジェクト
関数内で宣言されたローカル変数は、見えない(アクセスできない)変数オブジェクトに登録されます。
関数宣言の内側、つまりローカルスコープにおける変数オブジェクトとは何でしょうか。
その答えは、「目に見えない(アクセスできない)変数オブジェクトが自動的に作られる」というものです。
関数呼び出しが発生すると、対応する変数オブジェクトが自動的に作られます。
JavaScriptクロージャを完全理解!スコープチェインを知る(前編) - page3 - builder by ZDNet Japan
変数を参照する仕組み(スコープチェーン)
JavaScriptでは、変数を参照する際にローカルスコープから外側のスコープへと、順々に変数を探していきます。以下の例では、A〜Cへスコープを検索していき、最終的にグローバルスコープにある変数xの内容が出力されます。
var x = 'global'; // ----- (C) function A() { // ----- (B) function B() { return x; // ----- (A) } return B(); } alert(A()); // >>global
JavaScriptとレキシカルスコープ
JavaScriptはレキシカルスコープというスコープを採用しています。こちらについては、以下のエントリを参照してください。