小さい頃はエラ呼吸

いつのまにやら肺で呼吸をしています。


初心者のためのJavaScriptのスコープ

はじめに

JavaScriptのスコープについて、あらためて勉強する機会があったので、まとめてみました。

よくわかるJavaScriptの教科書
たにぐち まこと
マイナビ
売り上げランキング: 11,386

プログラミングにおけるスコープ

はじめに、そもそもスコープとは何なのかを理解する必要があります。プログラミングにおけるスコープとは、変数や関数の有効範囲のことを言います。ある変数や関数が見える範囲と言い換えてもいいかもしれません。

プログラミングでのスコープとは、ある変数や関数が特定の名前で参照される範囲のこと。ある範囲の外に置いた変数等は、通常、その名前だけでは参照できない。このときこれらの変数はスコープ外である、「見えない」といわれる。
スコープ - Wikipedia はてなブックマーク - スコープ - 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 はてなブックマーク - 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 はてなブックマーク - 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 はてなブックマーク - 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クロージャを完全理解!スコープチェインを知る(前編) - 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はレキシカルスコープというスコープを採用しています。こちらについては、以下のエントリを参照してください。