スコープ
JavaScriptでは、変数が使える範囲が決まっています。
本ページでは、スコープについて説明します。
スコープとは
スコープとは、変数が使える範囲のことです。
例えば、xという変数を宣言した場合、スコープの外ではxが使えません。また、同じスコープ内では、xという変数名は1つしか使えません。スコープの外では、同じ変数名を違う変数として扱えます。
スコープには、以下があります。
- グローバルスコープ
- ローカルスコープ
ローカルスコープには、ブロックスコープと関数スコープがあります。
ブロックスコープ
以下は、ブロックスコープの例です。
{ let x = 1; const y = 1; var z = 1; } let a = x; let b = y; let c = z;
左中括弧({)と右中括弧(})で囲まれた範囲は、ブロック文です。複数の処理をグループ化できます。
ブロック文の中でletとconstを使って宣言した場合、ブロックスコープを持ちます。ブロックスコープによって、変数xとyが使える範囲はブロック文の中だけになります。このため、let a = xとlet b = yは、エラーになります。
varで宣言した場合、ブロックスコープを持ちません。このため、変数zはブロック文の外でも使え、let c = zはエラーにならずcに1が代入されます。
ブロックスコープ内でしか使えないことのメリット
一見、varの方が使い勝手が良さそうですが、ブロックスコープ内でしか使えないことにはメリットがあります。
let x = 1; var y = 1; { let x = 2; var y = 2; } let a = x; let b = y;
上記は、aは1になりますが、bは2になります。xは、ブロック文の中と外で違う変数として扱われているため、スコープの中ではxは2ですが、スコープの外ではxが1になるためです。
つまり、ブロック文の中で宣言した変数は、スコープ外の同じ名前の変数に影響を与えないようにできます。ブロックスコープ内だけで変数の名前を管理すれば良いため、変数の管理が楽になります。
if文やfor文などのブロックスコープ
ブロックスコープは、if文やfor文など{}で囲まれた範囲でも有効です。以下は、例です。
if ( x + y === 10 ) { let z = 1; }
このzは、ブロックスコープの中だけで使えます。
関数スコープ
関数にもスコープがあります。以下のスクリプトがあったとします。
function func1() { let x = 1; } func1(); let y = x;
これは、エラーになります。関数内で宣言した変数は、同じ関数内だけで使えるためです。この範囲を、関数スコープと言います。
ブロックスコープと違って、varで宣言した場合も関数スコープを持ちます。つまり、関数の外では使えません。
メリットは、ブロックスコープの時と同じです。
グローバルスコープ
ブロックスコープと関数スコープを合わせて、ローカルスコープと言います。ローカルスコープを持つ変数は、ローカル変数と呼ばれます。
ローカルスコープに対して、スクリプト全体で使える範囲をグローバルスコープと言います。以下は、例です。
let x = 1; { let y = 1; }
上記で、xが使える範囲はグローバルスコープになります。使える範囲がグローバルソコープの変数を、グローバル変数と言います。yは、ブロック文内で宣言しているためローカル変数です。グローバル変数のxは、ブロック文の中でも使えます。
以下も、グローバル変数です。
{ x = 1; }
letなどを使わないで宣言した変数は、すべてグローバル変数になります。
これは、すでに使っているグローバル変数を書き換えるため、スコープを明確にするためにもletなどを使って宣言することが推奨されます。
スコープチェーン
グローバル変数は、ブロック文内で使えると説明しました。このため、以下はaが1になります。
let x = 1; { let a = x; }
let a = xのxは、外側のスコープを参照しています。これを、スコープチェーンと言います。
しかし、以下はエラーになります。
let x = 1;
{
let a = x;
let x = 2;
}
これは、ブロック文内でlet x = 2と宣言しているためです。これで、グローバル変数のx(=1)と、ローカル変数のx(=2)が別の変数として扱われることになります。つまり、ローカル変数xを宣言する前にlet a = xで代入しようとしたためエラーとなります。
上記を、letの代わりにすべてvarで宣言した場合、ブロックスコープを持たないためエラーになりません。つまり、aは1となり、xは2となります。
以下は、もう1つの例です。
{ let x = 1; { let a = x; } }
これは、ブロック文がネスト(入れ子)されていますが、let a = xのxは外側で宣言されているxが1の値を利用します。
このように、内部で変数が宣言されていない時、スコープチェーンによってより外側で宣言された変数を参照するようになっています。内部でletやconstで変数が宣言されると、外側で宣言された変数と名前が同じであっても、別物になります。
また、ブロックスコープだけでなく、関数スコープでもスコープチェーンは発生します。違いとしては、varで宣言した場合でも関数スコープを持つため、letやconstで宣言した時と同じ動きになります。
これらをまとめると、以下になります。
- 宣言してスコープを持つと、スコープ内だけで使える変数ができる。
- 宣言しないと、スコープチェーンによって外で宣言された変数が使える。
即時関数とブロック文
変数がスコープ内でしか使えないことはメリットがあるため、なるべくグローバル変数を使わずに、ローカル変数を使おうと考えられたのが即時関数です。
(function () { let x = 1; let y = 2; let z = x + y; })();
x、y、zはローカル変数なので、スコープの外では使えませんが、スコープの外では同じ変数名を異なる変数として利用ができます。また、今回の例では関数名なども不要なので、名前を節約できます。
即時関数は、呼び出しもなく実行されます。つまり、以下と結果は同じです。
let x = 1; let y - 2; let z = x + y;
しかし、上記ではx、y、zがグローバル変数になります。
即時関数は、変数をスコープ内に閉じ込めるのに便利ですが、今ではブロック文が使えるようになりました。
{ let x = 1; let y = 2; let z = x + y; }
これだけで、x、y、zはローカル変数になるので、即時関数を使う必要がありません。
一時的な変数を使う時は、ブロック文を使ってletなどで宣言すれば、これまで使っているグローバル変数を気にせずに使えます(同じ変数名を使っても、グローバル変数の方の値は変わりません)。