JavaScript 実行コンテキスト
O'REILLY JavaScript 第3版 のスコープあたりを読んでいて混乱してきたので、整理がてらメモ。
Callオブジェクト
(スタックフレームのようなもの)
実行コンテキスト==グローバル/Callオブジェクトっぽい書きかたがしてある。
実行コンテキスト毎に1つのスコープチェーンが存在する。関数が実行されて新しいコンテキストが作られるとスコープチェーンも作られる。
最も基本的なケースであるグローバル領域で関数を定義した場合は、関数定義時はグローバルオブジェクト一つのスコープチェーンであるため、これを使用することになる。このチェーンに呼び出した関数のCallオブジェクトが追加される。
以下に例を示す。
(1) f(),g()定義時のスコープチェーン
(2) f()が呼び出されると...
このあたりはCとかと同じなので問題なし。
(3)f()からg()が呼び出されると...
g()から参照できるのはz,xのみ。これも問題なし。
(4) g()定義時のスコープチェーン
(5) f()を呼び出して、f()からg()が実行されると...
g()からはx,y,zが参照可能。このあたりもまだ、直感的にOK。
関数{return x}はf(x)を実行するたびに作成(定義)される。このため、f(1),f(2)で{return x}が作成された時のそれぞれのスコープチェーンは以下のようになる。
(6) f(1)でreturn xが定義されたときのスコープチェーン
(8) func1()呼び出し時のスコープチェーン
(9) func2()呼び出し時のスコープチェーン
よって、func1()では1が返り、func2()では2が返るようになる。
1.オブジェクト
グローバルオブジェクトグローバル変数がオブジェクトのプロパティとして格納される。常に1つだけ存在する。
Callオブジェクト
関数の実行中、変数や引数がオブジェクトのプロパティとして格納されている。関数を実行する度に作成される。
2.実行コンテキスト
関数を実行するたびに新しいコンテキストを作成する。スタック構造をしており、スタックのTopが現在の実行コンテキスト。(スタックフレームのようなもの)
- 関数外部(グローバル)のJavascriptコード実行時の実行コンテキストはグローバルオブジェクト
- 関数内部のJavascriptコード実行時の実行コンテキストは関数実行時に作成されたCallオブジェクト
実行コンテキスト==グローバル/Callオブジェクトっぽい書きかたがしてある。
3.スコープチェーン
スコープチェーンはグローバル/Callオブジェクトのリスト。変数を参照する際に、スコープチェーンの末端から順番に探される。リストのトップはグローバルオブジェクトとなる。実行コンテキスト毎に1つのスコープチェーンが存在する。関数が実行されて新しいコンテキストが作られるとスコープチェーンも作られる。
4.静的スコープ
関数実行時に使用するスコープチェーンは関数定義時に決定される(関数が定義された時点のスコープチェーンが使用される)。最も基本的なケースであるグローバル領域で関数を定義した場合は、関数定義時はグローバルオブジェクト一つのスコープチェーンであるため、これを使用することになる。このチェーンに呼び出した関数のCallオブジェクトが追加される。
以下に例を示す。
4.1 グローバル領域で関数定義をした場合
x=1;
function f() {var y=2;g();}
function g() {var z=3;}
f();
(1) f(),g()定義時のスコープチェーン
+------+f(),g()にはこのスコープチェーンが使用される。
| x=1 | Global Obj.
+------+
(2) f()が呼び出されると...
+------+(1)のスコープチェーンにf()を呼び出した時に作成されたCall Obj.がチェーンされる。y,xはスコープチェーンにあるので参照できるが、zは参照できない。
| x=1 | Global Obj.
+------+ <--+
|
+------+ ---+
| y=2 | f() Call Obj.
+------+
このあたりはCとかと同じなので問題なし。
(3)f()からg()が呼び出されると...
+------+g()も(1)のスコープチェーンを使用するので、(1)にg()を呼び出した時に作成されたCall Obj.がチェーンされる。
| x=1 | Global Obj.
+------+ <--+
|
+------+ ---+
| z=3 | g() Call Obj.
+------+
g()から参照できるのはz,xのみ。これも問題なし。
4.2 関数領域に関数定義をした場合
x=1;
function f()
{
var y = 2;
g();
function g() {var z=3;}
}
f();
(4) g()定義時のスコープチェーン
+------+f()が呼び出されて初めてg()が定義されている
| x=1 | Global Obj.
+------+ <--+
|
+------+ ---+
| y=2 | f() Call Obj.
+------+
(5) f()を呼び出して、f()からg()が実行されると...
+------+g()が定義された時のチェーンは(4)なので、(4)にg()呼び出し時のCall Obj.がチェーンされる。
| x=1 | Global Obj.
+------+ <--+
|
+------+ ---+
| y=2 | f() Call Obj.
+------+ <--+
|
+------+ ---+
| z=3 | g() Call Obj.
+------+
g()からはx,y,zが参照可能。このあたりもまだ、直感的にOK。
4.3 呼び出される度に関数を返す関数
function f(x)
{
return function () {return x}
}
func1 = f(1);
func2 = f(2);
alert(func1()); <== 1と表示
alert(func2()); <== 2と表示
関数{return x}はf(x)を実行するたびに作成(定義)される。このため、f(1),f(2)で{return x}が作成された時のそれぞれのスコープチェーンは以下のようになる。
(6) f(1)でreturn xが定義されたときのスコープチェーン
+------+(7) f(2)で{return xが定義されたときのスコープチェーン
| | Global Obj.
+------+ <--+
|
+------+ ---+
| x=1 | f(1) Call Obj.
+------+
+------+このため、func1(),func2()を呼び出すとそれぞれ、以下のスコープチェーンになる。
| | Global Obj.
+------+ <--+
|
+------+ ---+
| x=2 | f(2) Call Obj.
+------+
(8) func1()呼び出し時のスコープチェーン
+------+(6)にfunc1()呼び出し時のCall Obj.がチェーンされる。
| | Global Obj.
+------+ <--+
|
+------+ ---+
| x=1 | f(1) Call Obj.
+------+ <--+
|
+------+ ---+
| | {return x} Call Obj.
+------+
(9) func2()呼び出し時のスコープチェーン
+------+(6)にfunc2()呼び出し時のCall Obj.がチェーンされる。
| | Global Obj.
+------+ <--+
|
+------+ ---+
| x=2 | f(2) Call Obj.
+------+ <--+
|
+------+ ---+
| | {return x} Call Obj.
+------+
よって、func1()では1が返り、func2()では2が返るようになる。