スコープその1

JavaScriptを勉強しているんだけど,スコープはクロージャのベースともなる大事な仕様だと思ったのでいろいろ調べました。その備忘録です。

まだまだJavaScriptは初学者なので,ここ違うぞ!とか有りましたらコメント頂けると助かります。

そもそもスコープとは?

JavaScriptというよりプログラムの一般的な話でスコープとはコード中の変数や関数の名前解決を行う為の有効範囲です。

例えば,スコープ外で変数や関数が使われている場合,

function func()
{  
  var a = 'TEST';
  return 0;
}
document.writeln(a);  

実行してみるとエラーとなります。

関数内で宣言されているaは有効範囲が関数の中だけなので,トップレベル(どの関数の中でもない場所)でaという変数を使うことはできません。

スコープの種類

JavaScriptのスコープはグローバルスコープとローカルスコープの二種類があります。

C++等にある中括弧で囲まれたブロックのスコープはありません(例えばfor文の中で宣言した変数はfor文の外でも使えます)。

グローバルスコープ

グローバルスコープをもつ変数はコード上のあらゆる場所で使う事ができます。
関数の中で宣言されていない変数や関数がグローバルスコープをもつことになります。

// この変数aはグローバル
var a;
// このfunc関数はグローバル
function func()
{
  // この変数bや関数func2はグローバルではない
  var b;
  function func2()
  {
  }
}
グローバルオブジェクト

グローバルスコープの仕組みはグローバルオブジェクトにあります。

JavaScriptインタプリタはコードを実行する時,まずグローバルオブジェクトを作ります。
そしてグローバルスコープをもつ変数や関数は全て,このグローバルオブジェクトのプロパティとなります。
インタプリタが処理中に名前(変数あるいは関数)が出てくるとこのグローバルオブジェクトのプロパティから同じものがないか探します。

(ちなみにブラウザで動かす場合はグローバルオブジェクトはWindowオブジェクトの事です)

ローカルスコープ

関数の中で作られたローカル変数や関数がもつのがローカルスコープです。
その関数の中でのみ使うことができます。

function func()
{
  var tmp = 0;// この関数の中で有効
  document.writeln(tmp);
}
Callオブジェクト

ローカルスコープの仕組みはCallオブジェクトにあります。

インタプリタは関数の実行時にCallオブジェクトを作ります。
Callオブジェクトはその関数内で宣言されている変数や関数をプロパティにもちます。
もちろん仮引数もCallオブジェクトのプロパティに入ります(つまり仮引数はローカルスコープをもつ事になります)。

インタプリタが関数内を処理中に名前にであうとそれがCallオブジェクトのプロパティにないか探しに行きます。


ちなみにCallオブジェクトをアクティベーションオブジェクトと呼ぶ場合もあるそうですが,どっちも同じ意味です。

スコープチェーン

もし関数のなかに出てきた名前がCallオブジェクトとグローバルオブジェクトのどちらにもあったらどうなるか?
Callオブジェクトのものが優先されます。

この,変数あるいは関数の名前解決を行う優先順位の仕組みをスコープチェーンといいます。

スコープチェーンは関数の構文構造(関数の処理が書かれているコード)を元に内側の関数から外側の関数そしてグローバルの向きに優先順位が低くなります。

例えば次の場合

var a = 0;
function func1()
{
  var a = 100;
  function func2()
  {
    a = -1;
    return a;
  }
  func2();
}

上記のコードでfunc2が実行されたとき,変数aを探す順序は

  1. func2のCallオブジェクトのプロパティ
  2. func1のCallオブジェクトのプロパティ
  3. グローバルオブジェクトのプロパティ

となります。

Functionコンストラクタのスコープチェーン

ちなみに仕様では,Functionコンストラクタを使って関数を作り実行した場合のスコープチェーンは自分自身のCallオブジェクトの次はグローバルオブジェクトを探すようになっています。