JavaScriptにおけるスコープとは?
スコープとは、一言で言うと、見える範囲のことです。
また、JavaScriptにおける具体的な意味としては、「その場所から、必要な変数の値を参照できるか?」ということになります。
例えば、あるファンクションの外側から、そのファンクションの内側で宣言されたローカル変数の値を参照することはできません。その場合は、「その変数に対してスコープがない」ということになります。
逆に、ファンクションの内側から、ファンクションの外で宣言されたグローバル変数を参照することができますので、その場合は、「その変数に対してスコープがある」ということになります。
基本的には、3つのタイプのスコープがあります。
- ブロックスコープ(Block Scope)
- ファンクションスコープ(Function Scope)
- グローバルスコープ(Global Scope)
Block Scope
最初にブロックスコープですが、こちらはES6(ECMAScript 2015)において新たに追加されたletとconstが提供するスコープとなります。
if文、switch文、forループなどの波括弧{ }に挟まれた部分(波括弧{ }に挟まれた場所をコードブロックといいます)においてletまたはconstで宣言された変数は、そのコードブロックの範囲がスコープとなります。
例えば、if文の波括弧{ }に挟まれたコードブロック内でlet name1 = "Satoshi" と宣言した場合、その波括弧の外側から変数name1の値を参照することはできません。
ただし、letやconstと異なり、varを使った変数はファンクションスコープであり、ブロックスコープではないので、コードブロックによるスコープの制限を受けません。
const start = Date.now();
if (start) {
let name1 = "Satoshi";
}
console.log("name1:", name1);
// let nameはconsole.log()がある場所のスコープに存在しません。そのため、console.log()の場所から見ると、
// 変数として宣言されておらず、かつ、値がない状態となり、ブラウザで実行するとreferenceErrorとなります。
ヒント
例えば、上記のサンプルコードでは、let name1の部分をvar name1に変えると、5行目のconsole.log()の場所からname1の値を参照できるようになります!
Function Scope
ファンクションスコープですが、これは最も基本的なスコープで、もともとJavaScriptでは、「ファンクションがスコープを作る」と言われてきました。
こちらは、var、let、constのいずれであっても、ファンクションの{ }の内側(ファンクションボディ)で宣言した場合、そのファンクションボディの中だけがスコープとなります。
function test () {
var name1 = "Satoshi";
let name2 = "Mizuki";
const name3 = "Manatsu";
const start = Date.now();
if (start) {
var name4 = "Ayaka";
let name5 = "Gonzo";
const name6 = "Julian";
}
console.log("name1:", name1); //同じファンクションボディの中なので、見える
console.log("name2:", name2); //同じファンクションボディの中なので、見える
console.log("name3:", name3); //同じファンクションボディの中なので、見える
console.log("name4:", name4); //var name4は、コードブロック内で宣言されているが、ファンクションスコープなので、見える
console.log("name5:", name5); //let name5は、コードブロック内で宣言されており、外から見えない => reference error
console.log("name6:", name6); //const name6は、コードブロック内で宣言されており、外から見えない => reference error
}
test();
console.log("name1-2:",name1); //ファンクションの外からは見えない => reference error
まとめ
- varで宣言した変数はファンクションスコープを提供し、if文などのコードブロックによる制限は受けません。
- letまたはconstで宣言した変数はブロックスコープを提供し、ファンクションおよびコードブロックによる制限を受けます。
Global Scope
varの場合は、ファンクションの外側で宣言すると、グローバル変数となり、そのスクリプト範囲(一連のコード)のどこからでも参照できる状態になります。
また、letやconstの場合は、ファンクションの外側かつコードブロックの外側で宣言すると、グローバル変数となります。
何がスコープを作るのか?
スコープは、下記の要素が原因となり、形成されます。
- コードブロック
- ファンクション
- モジュール
非常に多くの場合、コードブロック(基本的にペアの波括弧の中がコードブロックになる)とファンクションがスコープを制限します。
また、モジュール(JavaScriptモジュール)を使用する場合は、各モジュールの中でJavaScriptが利用する変数、ファンクションについてのスコープは、そのモジュールに閉じられています。つまり、明示的に相互にexport, importの記述をしない限り、他のモジュールの機能を利用したり、他のモジュール内で宣言されている変数を参照することはできません。
JavaScriptモジュールを構成する場合は、<script type="module" src="main.js"></script>などのように、scriptタグの中でtype="module"と明示した状態で、htmlファイルのヘッド部またはボディ部へそのタグを挿入して、JavaScriptファイルを読み込みます。この際、main.jsの下で複数のJavaScriptファイルを束ねることが可能です。
ファンクションが入れ子になっている場合のスコープはどうなっているか?
function outerFunc() {
var monster = 'Mozilla';
function innerFunc(newMonster) {
//ここの行にある変数monsterは、2行上にあるmonsterに対してスコープを持っている
newMonster ? monster = monster + newMonster: null;
console.log(monster)
}
return innerFunc;
}
//上記のファンクションinnerFuncをまるごとmyFuncへ代入します
var myFunc = outerFunc();
myFunc(); //コンソール出力はMozilla
myFunc("Gozilla"); //コンソール出力はMozillaGozilla
myFunc("Vanilla"); //コンソール出力はMozillaGozillaVanilla
ファンクションが入れ子になっている状態とは、上記のコードのように、外側ファンクションのボディの中に内側ファンクションがまるごと入っている状態を指します。
下記の図は、ファンクションが入れ子になっている場合の概念図です。
ファンクションが入れ子になった場合ですが、スコープは下記のとおりです。
- function innerのボディ(グリーン)から見て、その自分自身の範囲(グリーン)と、function outerのボディ(ブルー)にある変数に対してスコープを持っています。
- function outerのボディ(ブルー)から見ると、function innerのボディ(グリーン)へのスコープはありません。
- 白い部分のグローバル・スコープ(Global Scope)において宣言された変数をグローバル変数といいますが、グローバル変数については、グリーン、ブルーのどこからでも参照可能です。
ファンクションが2重に入れ子になった場合のスコープはどうなっているか?
さらに内側のファンクションがある場合ですが、考え方は同じです。
- 最も内側にあるfunction inner2のボディ(イエロー)から見ると、その自分自身のファンクションボディは当然ですが、それに加えて、その一つ外側のfunction inner1のボディ(グリーン)、さらに、一番外側のfunction outerのボディ(ブルー)に対して、スコープを持っています。
- function inner1のボディ(グリーン)から見ると、外側のfunction outerのボディ(ブルー)に対するスコープはありますが、最も内側にあるfunction inner2のボディ(イエロー)へのスコープはありません。
- function outerのボディ(ブルー)から見ると、グリーンとイエローのエリアに対してはスコープがありませんので、見えません。
- もちろん、グローバル変数に対しては、イエロー、グリーン、ブルーのどこからでも参照可能です。
varとlet/constのスコープにおける違い
varとlet/constのスコープにおける違いですが、letおよびconstは、ブロックスコープを提供しますので、if文やfor loopの波括弧の内側(コードブロック)で宣言した場合、そのコードブロックの中だけがスコープとなります。
また、varはブロックスコープではありませんので、if文の波括弧の中で宣言しても、その外側から、その値を参照することができます。
const start = Date.now();
if (start) {
var name1 = "Satoshi";
let name2 = "Erika";
}
console.log("name1:", name1); //varはブロックスコープではないので、if文の外から値を参照できる。
console.log("name2:", name2); //letはブロックスコープなので、if文の外から値を参照することはできない => reference error