2016.8.27追記
ES2015で登場したlet
とconst
はブロックスコープを持つので、そちらも参照されたい。
var,let,constの違いは、ブロックスコープと巻き上げ
グローバルスコープとローカルスコープ
スコープとは、変数を参照できる範囲のこと。
スコープ外の変数には、アクセス出来ない。
JavaScriptにおけるスコープは、以下の3つ。
- グローバルスコープ
- ローカルスコープ
eval
スコープ
このうちeval
スコープについては、実務で使うことはまずないと思われるので、ここでは触れない。
JavaScriptは必ず、グローバルスコープを1つ持つ。
全てのコードは、グローバルスコープ(グローバルオブジェクト)の中に書かれている、と言える。
グローバルスコープにある変数(グローバル変数)には、どこからでもアクセスできる。
ローカルスコープは、関数ごとに作られる。
ローカルスコープにある変数(ローカル変数)には、その関数の中でのみ、アクセスできる。
// グローバルスコープ var a = 'This is global.'; (function(){ // ローカルスコープ var x = 'x'; console.log(a); // This is global. console.log(x); // x })(); (function(){ // ローカルスコープ var y = 'y'; console.log(a); // This is global. console.log(y); // y })(); console.log(a) // This is global. console.log(x); // ReferenceError: x is not defined
ifやforはスコープを持たない
JavaScriptのスコープは、上述の3種類だけである。
if
やfor
の{}
はスコープを持たない。
if(true){ var i = 0; }; console.log(i); // 0 i = 99; console.log(i); // 99 for(var x=0; x < 1; x++){ i++; console.log(i); // 100 }; console.log(x); // 1 // ifやforの{}は、スコープを持たない
スコープチェーン
ローカルスコープで該当する変数が見つからなかった場合、グローバルスコープから変数を探す。
var x = 'x of global.'; var y = 'y of global.'; (function(){ var x = 'x of local.'; console.log(x); // x of local. console.log(y); // y of global. })();
関数が入れ子になっている場合は、処理が発生したスコープから順番に辿っていく。
var x = 'x of global.'; var y = 'y of global.'; var z = 'z of global.'; var outerScope = function(){ var x = 'x of outer'; var y = 'y of outer'; var innerScope = function(){ var x = 'x of inner'; console.log(x); console.log(y); console.log(z); console.log(a); }(); }; outerScope(); // x of inner // y of outer // z of global. // ReferenceError: a is not defined
上記の例ではまず、innerScope
のローカルスコープを探す。次にouterScope
のローカルスコープを探す。次にグローバルスコープ。
この一連のプロセスは、グローバルスコープに行き着くまでどこまでも続く。
そしてグローバルスコープでも見つからなければ、エラーとなる。
この仕組みのことを、スコープチェーンと呼ぶ。
ローカル変数の有効範囲
スコープチェーンはシンプルな仕組みだが、var宣言のタイミングには注意が必要。
var scope = 'This is global.'; var myFunc = function(){ console.log(scope); var scope = 'This is local.'; console.log(scope); }; myFunc(); // undefined // This is local.
上記の例では、最初のconsole.log(scope);
でなぜかundefined
を返している。
次の行でscope
を定義しているので、このローカルスコープでは、scope
が有効になっている。
だから、スコープチェーンを辿ってThis is global.
を返すことはない。
しかし実際に宣言して値を定義しているのは、ローカルスコープの2行目である。
そのため、最初のconsole.log(scope);
の時点では、ローカル変数scope
が「存在するが未定義」という状態になってしまい、undefined
を返したのである。
スコープは定義時に決まる
JavaScriptの関数は高階関数なので、引数として渡したり、戻り値として受け取ったりすることが出来る。
しかしスコープは、それらに影響されることはない。
関数がどのように呼び出されるかは、スコープに影響を与えない。
スコープはあくまでも、定義時に決まり、それが変わることはない。
この仕組みを利用したのが、クロージャである。
クロージャ
クロージャとは、ローカルスコープを保持した、関数内関数である。
下記の例では、無名関数がクロージャである。
function originFunc(){ var i = 'local'; return function(){ console.log(i); }; }; var closure = originFunc(); closure(); // local
無名関数は、スコープチェーンを辿って、変数i
にアクセスできる。
その無名関数が closure
に格納されている。closure
を実行した場所はグローバルスコープであり、originFunc
のスコープにはアクセスできないはずだが、closure
に格納されている無名関数がローカルスコープを保持しているため、i
にアクセスできる。
繰り返しになるが、スコープは関数の定義時に決まる。その関数がどのような文脈で呼び出されるかは無関係である。
そのため下記のoriginFunc()
はglobal
を返す。
var x = 'global'; function originFunc(){ console.log(x); }; (function(){ var x = 'local'; originFunc(); })(); // global