
この記事ではスコープについて説明します。
聞き慣れない言葉かもしれませんが、変数を正しく扱うために重要な概念です。
スコープを意識していないと予期せぬバグを生むことにもつながるので、ぜひ抑えておきましょう。
グローバル変数、ローカル変数といった用語も、スコープについて学ぶと正しく理解する事ができます。
この記事の目次
スコープとはなにか?
簡単にいうと、スコープとは変数の有効範囲のことです。
有効範囲外(スコープ外)の変数は呼び出しても扱うことが出来ず、エラーが発生します。
宣言した場所によって変数のスコープは変化し、その変数を扱える範囲も変わってくるので、
スコープを意識せずに変数を定義していると、予期せぬバグを発生させることにつながります。
スコープの必要性
スコープがない、つまり全ての変数の有効範囲がプログラム全体に及ぶ場合、
同じ名前の変数を宣言すると競合が発生するので、
変数の名前を全て一意なものにしなければならなくなります。
しかし、JavaScriptにはスコープがあるので、
同じ名前の変数でも宣言する場所が違えば、別の変数だとみなしてくれるのです。
また、変数を定義するとデータがメモリ上に確保されます。
ハードウェアのメモリは有限なので、不要になったデータ(変数)は
積極的に削除(開放)する必要があるのですが、
JavaScriptではこれをガベージコレクションというシステムで行っています。
ガベージコレクションとは、どこからも参照されなくなった不要なデータを、
自動的に削除する仕組みなのですが、不要かどうかの判定はスコープによって行われます。
つまり、処理がスコープ外に出た時そのスコープ内の変数は全て破棄され、メモリが開放されるのです。
JavaScriptにおける全スコープ
JavaScriptという言語におけるスコープは3つだけです。(大きく分けると2つ)
決して難しい概念ではないので、1つずつ見ていきましょう。
グローバルスコープ
グローバルスコープは最も広いスコープです。
プログラムの最も外側(トップレベル)で宣言された変数はグローバル変数と言い、
プログラムのどこからでもアクセス出来るグローバルスコープを持つことになります。
let foo = "foo";
function fn(){
console.log(foo); //アクセス可能 =>"foo"
}
fun();
for(let i=0; i<10; i++){
console.log(foo); //アクセス可能 =>"foo"
}
ローカルスコープ
グローバルスコープ以外のものをローカルスコープと呼びます。
グローバル変数以外のすべての変数はローカル変数であり、
ローカルスコープを持つことになります。
JavaScriptではローカルスコープは2つにわけることが出来ます。
関数スコープ
関数スコープは、関数ごとに作られるスコープです。
関数の中で宣言した変数は関数スコープを持つことになり、
その関数の中でしか扱うことが出来ません。
尚、関数の仮引数も関数スコープをもちます。
function fn(arg){
let foo = "foo";
console.log(foo); //同じ関数内なので参照可能。 => "foo"
}
fun();
//関数スコープの外側なので、fooやargsにはアクセスできない
console.log(foo); // => ReferenceError
console.log(arg); // => ReferenceError
ブロックスコープ
ブロックスコープは、ブロック{}ごとに作られるスコープです。
for、while、if、switch文などで宣言された変数はブロックスコープを持ち、
その制御文におけるブロック内でのみ扱うことが出来ます。
//変数iはfor文のブロックスコープを持つ。
for(let i=0; i<10; i++){
if(i == 0){
let foo = "foo"; //変数fooはif文のブロックスコープを持つ。
console.log(i); //for文の中なのでアクセス可能 => 0
console.log(foo); //if文の中なのでアクセス可能 => "foo"
}
console.log(i); //for文の中なのでアクセス可能 => 0~9
console.log(foo); //if文の外なのでアクセスできない => ReferenceError
}
//ブロックスコープの外側なので、iやfooにはアクセスできない
console.log(i); // => ReferenceError
console.log(foo); // => ReferenceError
宣言の巻き上げ
スコープに関連する巻き上げという概念についても簡単に解説しておきます。
varによる変数宣言の巻き上げ
以下のコードは、変数を定義していないのでエラーが出ます。
console.log(foo); // => ReferenceError
次に、以下のコードではどうでしょうか?
console.log(foo); // => ?
var foo = "foo";
プログラムのコードは基本的に上から下に順に実行されていきます。
なので一見すると、1行目の時点では変数の宣言がまだ行われておらず、
今回も同じように ReferenceError が出るように見えると思います。
しかし、実際は undefined (未定義)が出力されます。
これは、JavaScriptでは変数の巻き上げが行われており、
上記のコードは、内部で自動的に以下のように変更されているためです。
var foo; // 宣言のみ行う。
console.log(foo); // => undefined
foo = "foo"; // 変数の代入は元々あった位置で行われる。
JavaScriptでは、var変数の宣言はスコープの最初で行われるのです。
これが変数宣言の巻き上げという概念になります。
以下のようなコードは、
function fn(){
console.log(foo); // => undefined
var foo = "foo";
if(foo == "foo"){
console.log(bar); // => undefined
var bar = "bar";
}
}
fn();
巻き上げが起こり、以下のコードと同じ意味を持ちます。
function fn(){
var foo;
console.log(foo); // => undefined
foo = "foo";
if(foo == "foo"){
var bar;
console.log(bar); // => undefined
bar = "bar";
}
}
fn();
ちなみに、varを使った変数宣言は、ES5というJavaScriptの古いバージョンのものです。
現在はES6が使えるので、基本的には let や const で変数宣言を行うようにしましょう。
尚、let や const で変数宣言を行った場合は、
変数宣言の巻き上げは行われますが、出力は undefined ではなく ReferenceError になります。
巻き上げが起こると予期せぬバグに繋がる可能性があるので、
変数の宣言はスコープの最初にまとめて行うように心がけましょう。
関数宣言の巻き上げ
JavaScriptでは、関数の宣言も巻き上げます。
fn(); // => "foo"
function fn() {
console.log('foo');
}
ただし、以下のような形で宣言した場合は巻き上げられずエラーが出ます。
fn(); // => TypeError: fn is not a function
var fn = function() {
console.log('bar');
};
関数についても、巻き上げによるバグを防ぐために、宣言は呼び出しよりも上側で行うようにしましょう。