概要
実行コンテキストは計算の状態を表し、以下の事項を決定する。
- 名前解決のためのスコープ
- thisの値
- varによって宣言された変数がどのオブジェクトに付与されるか
実行コンテキストはスタックを成す。以下のタイミングで新しい実行コンテキストが作成され、スタックに積み上げられる。
- プログラムの実行開始時。
- 関数が呼び出されたとき。再起呼び出しの場合は、呼び出されるたびに新しい実行コンテキストが作成される。
- evalが実行されたとき。
それぞれ、プログラム終了時、関数終了時、evalの実行終了時に実行コンテキストから抜ける(スタックのトップの実行コンテキストが消える)。
実行コンテキストは以下の情報を持つ。
- thisの値
- スコープチェイン
- 変数オブジェクト
スコープチェイン
スコープチェインはオブジェクトのリストであり、識別子の評価時に参照される。
識別子fooを評価する際は、まずスコープチェインの先頭のオブジェクトからプロパティfooを検索する。見つからなければ、スコープチェインの次のオブジェクトから検索する。
スコープチェインは、実行コンテキストの新規作成時に作成されるほか、withステートメント、catch節の実行時に変更される。スコープチェインの最後まで探しても見つからない場合は「存在しない」扱いとなる*1(ECMAScript3rd:10.1.4)。具体的な挙動は識別子を利用する場所にもよるが、一般には
- 値の参照の場合→エラー (ECMAScript3rd:8.7.1)
- 値の代入の場合→グローバルオブジェクトのプロパティに代入 (ECMAScript3rd:8.7.2)
document.write("hogehogehoge="+hogehogehoge+"\n"); //→エラー
hogehogehoge="hogehogehogeValue";
document.write("hogehogehoge="+hogehogehoge+"\n"); //→hogehogehogeValue
// 識別子.プロパティ名 でプロパティが見つからなかったときには、undefinedとなる。
// スコープチェインが利用されるのは"識別子"部分の解決であって、"."から後ろの解決ではない。
var simpleObj={
foo: "fooVal"
};
document.write("simpleObj.hogehogehoge="+simpleObj.hogehogehoge+"\n"); //→undefined
変数オブジェクト
varで宣言された変数、functionで宣言された関数などは、実際にはプロパティとして扱われる。
これらのプロパティが付与される対象のオブジェクトが変数オブジェクト。
実行コンテキストの生成
プログラム開始時
プログラム開始時の実行コンテキスト(ECMAScript3rd:10.2.1)
- this: グローバルオブジェクト
- 変数オブジェクト: グローバルオブジェクト
- スコープチェイン: 長さ1、要素はグローバルオブジェクトのみ。
グローバルオブジェクト
プログラム実行中はグローバルオブジェクトというオブジェクトがあり、Math、String、Objectなどのビルトインオブジェクトはグローバルオブジェクトのプロパティとなっている。
関数実行時
関数実行時の実行コンテキスト(ECMAScript3rd:10.2.3)
- this:
- A.foo(...)のように呼び出した場合、thisはA。
- foo(...)のように呼び出した場合、thisはグローバルオブジェクト。
- スコープチェイン: 先頭はアクティベーションオブジェクト。のこりはfooの[[Scope]]と同じ。
- 変数オブジェクト: アクティベーションオブジェクト。
※スコープチェインは、呼び出した関数のスコープチェインを利用して決まる。
関数呼び出し元の実行コンテキストの影響は受けない。
アクティベーションオブジェクト
アクティベーションオブジェクトは、関数の実行開始時に作成される。関数の引数とその値が、アクティベーションオブジェクトのプロパティとして付与される。
※アクティベーションオブジェクトはECMAScriptの動作の説明のために導入された概念であり、プログラムから直接アクセスすることはできない。
関数が持つスコープチェイン
ファンクションfooの[[Scope]]プロパティの値(スコープチェーン)は、fooを定義したとき(関数オブジェクトfooが作成されたとき)の実行コンテキストのスコープチェーンと同じである。
例
thisの挙動
var hoge="Global object hoge";
function Foo(){
this.hoge="Foo object hoge";
}
//(1)
Foo.prototype.doSome=function() {
document.write("In doSome\n");
document.write("this.hoge="+this.hoge+"\n");
this.doOther();
};
//(2)
Foo.prototype.doOther=function() {
document.write("In doOther\n");
document.write("this.hoge="+this.hoge+"\n");
};
var fooObj = new Foo();
var doSomeFunc=fooObj.doSome; //(3)
var doOther=fooObj.doOther; //(4)
document.write("* fooObj.doSome()\n");
fooObj.doSome(); //呼び出し先でthisはfooObjとなる
document.write("* direct call\n");
doSomeFunc(); //呼び出し先でthisはグローバルオブジェクトとなる
var obj={
hoge: "obj object hoge",
doSome: Foo.prototype.doSome,
doOther: function(){document.write("dummy doOther\n");}
};
document.write("* obj.doSome()\n");
obj.doSome(); //呼び出し先でthisはobjとなる
上記の実行結果
* fooObj.doSome() In doSome this.hoge=Foo object hoge In doOther this.hoge=Foo object hoge * direct call In doSome this.hoge=Global object hoge In doOther this.hoge=Global object hoge * obj.doSome() In doSome this.hoge=obj object hoge dummy doOther
スコープの確認: Javaなどと違い、関数内で宣言した変数は、関数の実行が終了しても残る。
function foo() {
//新しいアクティベーションオブジェクトができて
var count=0; //そこにプロパティcountができる
return function(){
return count++; //そのcountをインクリメントする
};
}
var incrementer=foo();
document.write("incrementer()="+incrementer()+"\n"); //→0
document.write("incrementer()="+incrementer()+"\n"); //→1
document.write("incrementer()="+incrementer()+"\n"); //→2
var incrementer2=foo(); //incrementerとは別のアクティベーションオブジェクト
//ができる。countはincrementerとは別のcountになる
document.write("incrementer2()="+incrementer2()+"\n"); //→0
スコープの確認:
function aaa() {
var mark="aaa's mark";
return function bbb() {
return function ccc(){
return mark;
};
};
}
function hoge(x) {
var mark="hoge's mark";
return x();
}
var bbbInstance=aaa();
var cccInstance=hoge(bbbInstance);
document.write("cccInstance()="+cccInstance()+"\n"); //→aaa's mark
eval実行時
eval実行時の実行コンテキスト(ECMAScript3rd:10.2.2)
- this: eval呼び出し側と同一のオブジェクト
- スコープチェイン: eval呼び出し側と同じオブジェクトのリスト
- 変数オブジェクト: eval呼び出し側と同一のオブジェクト
※仕様には、呼び出し側の実行コンテキストがなかった場合、についての説明があるが、そういう場合がありうるのか??
function somefunc() {
//document.write("hogehoge="+hogehoge+"\n"); //→エラー
eval("var hogehoge='hogeValue';");
document.write("hogehoge="+hogehoge+"\n"); //→hogeValue
}
somefunc();
document.write("hogehoge="+hogehoge+"\n"); //→エラー
withステートメント
with(A) {...}
- Aをスコープチェインの先頭に追加し、
- ...部分を実行し、
- スコープチェインを元に戻す