関数の初期化とCallオブジェクト
大事なことなので、記憶を裏打ちしてみる。
<!doctype html><html><head><title></title></head><body><script> function fn() { alert(typeof a + ", " + a); // undefined, undefined alert(typeof b + ", " + b); // function, function() { alert("b2"); } b(); // alert("b2"); try { alert(typeof c + ", " + c); } catch(err) { alert("c was not found"); } function b() { alert("b3"); } function b() { // duplicity alert("b2"); } var a = 3, b = function() { alert("b1"); }; // overwrite b alert(typeof a + ", " + a); // number, 3 alert(typeof b + ", " + b); // function, function() { alert("b1"); } b(); // alert("b1"); try { delete b; } catch(err) { alert("delete b; fail"); } return b; } alert("return " + fn()); // return function() { alert("b1"); } </script></body></html>
- fn() を実行すると、Callオブジェクトが作成される
- 関数内の関数を全て検索しリストを作成する。リストには function b() { alert("b2)"; } が登録される。function b() { alert("b3"); } は b2 により存在が上書される。
- 関数内のvar文を全て検索しリストを作成する。リストには a, b が登録される
- Callオブジェクトに a と b が登録される。this や arguments も登録される
- Callオブジェクトの各プロパティが以下の値で初期化される
- Call.a = undefined;
- Call.b = function b() { alert("b2"); } (b = undefined ではない)
- Call.this = Globalオブジェクト(ブラウザなら window オブジェクト)
- Call.arguments = 実装依存 (see ECMAScript5)
- var a = 3; の行に到達すると a は 3 で初期化される。それまでは undefined で初期化されている
- var b = function() { alert("b1"); } の行に到達すると、Call.b が 上書される
- Call.c は宣言されておらず、Globalオブジェクトにも存在しないため、参照エラーが発生する
- delete b; で Call.b は消せない。例外も発生しない(暗黙)
- return b; が返すのは function() { alert("b1") } オブジェクト。関数を toString() した値が最後に表示される。
同じ関数が存在した場合は上書されてしまうから…
<!doctype html><html><head><title></title></head><body><script> function fn(a) { if (a === 1) { function b() { alert("a === 1"); } } else { function b() { alert("a !== 1"); } } return b; } fn(1)(); </script></body></html>
これは、 alert("a !== 1") が実行される。
記憶を頼りにイメージで書いてるから、初期化処理の正確な手順は ECMAScript の仕様書を見たほうがいいよ(Execution Contexts のとこ)
つ http://www2u.biglobe.ne.jp/~oz-07ams/prog/ecma262r3/ (ECMAScript3 日本語訳)