第7回 ecma-262 edition5.1読書会
DESCRIPTION
ECMA-262 Edition 5.1 読書会 (http://ecma262reading.doorkeeper.jp/) 第7回の発表資料です。TRANSCRIPT
ECMA 262Edition 5.1 読書会
第10条 実行可能コードと実行コンテキスト(10.4 〜 10.6)
About meShou Takenaka竹中 翔
@shoutakenaka
ソフトウェアエンジニア。最近はRoRとかAngularJSをやっています。
● https://twitter.com/shoutakenaka● https://github.com/shoutakenaka● https://plus.google.com/+ShouTakenaka/about● http://shoutakenaka.blogspot.jp/
10.1〜10.3の復習
● ECMAScriptのコードが実行される時、実行コンテキストが
生成される。
※ 仕様書ではLexEnv => LexivalEnvironment、VarEnv => VariableEnvironmentとなっているが、LexicalEnvironment仕様型と区別が付きにくいので本スライドでは
略記。
10.1〜10.3の復習
● 実行コンテキスト生成時は、LexEnv == VarEnv。● LexEnvは別のLexicalEnvironmentを参照することがある。
● VarEnvは不変。
● ResolveIdentifierは、LexEnv#GetIdentifierReferenceを呼
び出して識別子の解決を行う。
10.1〜10.3の復習
● LexicalEnvironmentはECMAScriptコード中のスコープに
対応するデータ構造。
10.1〜10.3の復習
● 値と識別子のバインディングを担当するEnvRecを持つ。
● 外側のLexicalEnvironment(=外側のスコープ)を表す
Outerを持つ。
● 識別子の解決(バインドされている値への参照の取得)を行
うGetIdentifierReferenceを持つ。○ 解決処理そのものはEnvRecに移譲。
● 新しいスコープに入る時、NewDeclarativeEnvironmentもし
くはNewObjectEnvironmentで子LexicalEnvironmentを作
成することができる。
10.1〜10.3の復習
● EnvironmentRecordは識別子と値のバインディングを管理
する。
10.1〜10.3の復習
● 識別子と値とのバインディングに関する各種操作を実装。
● DeclarativeEnvironmentRecord○ ローカル変数、関数のバインディングを管理するための
EnvironmentRecord。
○ DeclarativeEnvironmentRecord自体が内部でバインディング用のMapを持っているイメージ。
● ObjectEnvironmentRecord○ バインディングオブジェクトのプロパティのバインディグを管理するための
EnvironmentRecord。
○ 内部でバインディングオブジェクトの参照を持ち、このオブジェクトの内部メ
ソッドを呼び出してバインディングを管理する。
10.1〜10.3の復習
10.4 実行コンテキストの作成
● ECMAScriptコードが実行される時、実行コンテキストが生
成される。
○ globalコード
○ evalコード
○ functionコード
globalコード実行時
● LexEnv、VarEnvにグローバル環境(10.2.3参照)、ThisBindingにglobalオブジェクトを設定した実行コンテキス
トが生成される。
globalコード実行時
globalコード実行時
var foo = 1;
function bar() { console.log(“bar”);}
console.log(window.foo); //=> 1window.bar(); //=> “bar”
evalコード実行時
● 呼び出し元コンテキストが存在しない、もしくはdirect evalでない場合、globalコードの場合と同じ状態の実行コンテキス
トが生成される。
● そうでない場合は、呼び出し元実行コンテキストのLexEnv、VarEnv、ThisBindingと同じ値を参照した実行コンテキスト
が生成される。
evalコード実行時
globalコード実行時
eval( “var foo = 1;” + “function bar() {“ + “ console.log(‘bar’);” + “}”);
console.log(window.foo); //=> 1window.bar(); //=> “bar”
evalコード実行時
● strictの場合は、LexicalEnvironmentのNewDeclarativeEnvironment()を呼び出し、LexEnvがOuterに設定されたLexicalEnvironmentを作成し、それを
LexEnv、VarEnvに設定する。
evalコード実行時
evalコード実行時
“use strict”;eval( “var foo = 1;” + “function bar() {“ + “ console.log(‘bar’);” + “}”);
console.log(window.foo); //=> undefined//window.bar(); //=> TypeError
functionコード実行時
● 呼び出し時に指定されたthisArgをThisBindingに設定す
る。○ strictの場合はthisArgをそのままThisBindingに設定。
○ strictでない場合、
■ thisArgがnull、もしくはundefinedならglobalオブジェクトを
ThisBindingに設定。
■ null、undefined以外でObject型でない場合は、ToObjectして
ThisBindingに設定。
■ Object型なら、そのままThisBindingに設定。
● Functionオブジェクトの[[Scope]]内部プロパティの値
(8.6.2、13.2参照)を親にもつLexicalEnvironmentを生成
し、LexEnv、VarEnvに設定。
functionコード実行時
10.5 宣言的バインディングのインスタンス化
● 実行コンテキスト生成後、VarEnvが参照している
LexicalEnvironmentに、ローカル変数、関数、仮引数、
argumentsのバインディングが生成される。
a. functionコードの場合、各仮引数名を識別子、仮引数のインデックスに対応
する実引数を値(対応する実引数がない場合はundefined)として、バイン
ディングを設定。
b. コード中に関数定義が存在する場合は、関数名を識別子、Functionオブ
ジェクトを値として、バインディングを設定。
c. functionコード、かつargumentsがまだバインドされていない場合、10.6のアルゴリズムでargumentsを生成し、バインディングを設定。
d. コード中に変数宣言が存在する場合は、変数名を識別子、undefinedを値
として、バインディングを設定。
10.5 宣言的バインディングのインスタンス化
● このようなアルゴリズムになっているので、いわゆるローカ
ル変数のホイスティング(全ての変数宣言が関数の先頭で
行われたように振舞う=関数の途中で変数を宣言している
場合に、変数宣言よりも上の行でその変数を参照してもエ
ラーにならない)が起こる。ただし、代入文を評価するまで値
はundefined。
function foo() { console.log(a); //=> undefined var a = 1; console.log(a); //=> 1}
10.5 宣言的バインディングのインスタンス化
● 関数は、宣言的バインディングのインスタンス化時点で識別
子とFunctionオブジェクトがバインドされるので、いつでも呼
び出せる。
function foo() { bar(); //=> “bar called.” function bar() { console.log(“bar called”); } bar(); //=> “bar called.”}
10.5 宣言的バインディングのインスタンス化
● argumentsよりも仮引数と関数のバインディングが先に行
われるので、仮引数か関数にargumentsという識別子のも
のがあると、argumentsオブジェクトが生成されない。
(function(arguments) { console.log(arguments); //=> 1})(1, 2, 3);
(function() { console.log(arguments); //=> function arguments() {} function arguments() {}})(1, 2, 3);
(function() { console.log(arguments); //=> [1, 2, 3] var arguments = “arguments”; })(1, 2, 3);
10.6 argumentsオブジェクト
● functionコード、かつ仮引数か関数定義でargumentsという
識別子が存在しない場合に、以下のアルゴリズムで
argumentsオブジェクトが生成される。a. 新しいECMAScriptオブジェクトobjを生成する。
b. objのプロトタイプに標準組み込みObjectのプロトタイプオブジェクトを設定
する。
c. 名前がlength、値が実引数の数のプロパティをobjに追加する。
d. 指定された各実引数に対し、名前が実引数の位置(ゼロベースのインデッ
クス)、値が実引数値となるプロパティをobjに追加する。
e. strictでない場合
■ 名前がcallee、値がfunctionコードに対応するFunctionオブジェクト、と
なるプロパティをobjに追加する。
f. strictの場合
■ TypeErrorをスローするFunctionオブジェクトthrowerを作る。
■ 名前がcaller、値がthrowerのプロパティをobjに追加する。
■ 名前がcallee、値がthrowerのプロパティをobjに追加する。
10.6 argumentsオブジェクト
● strictでない場合、前述のcのステップの後に、arguments[n]と仮引数を同期させるための追加処理がある(p100 11のc)。○ strictでない場合は、arguments[n]を変更すると対応する仮引数の値も変
更される。逆も同様。
○ ちなみに、strictの場合はarguments[n]を変更しても対応する仮引数の値
は変更されないし、逆も同様。
10.6 argumentsオブジェクト
(function(a, b) { console.log(arguments[0]); //=> 1 console.log(arguments[1]); //=> 2 console.log(a); //=> 1 console.log(b); //=> 2
arguments[0] = 3; b = 4;
console.log(arguments[0]); //=> 3 console.log(arguments[1]); //=> 4 console.log(a); //=> 3 console.log(b); //=> 4})(1, 2);
10.6 argumentsオブジェクト
(function(a, b) { “use strict”;
console.log(arguments[0]); //=> 1 console.log(arguments[1]); //=> 2 console.log(a); //=> 1 console.log(b); //=> 2
arguments[0] = 3; b = 4;
console.log(arguments[0]); //=> 3 console.log(arguments[1]); //=> 2 console.log(a); //=> 1 console.log(b); //=> 4})(1, 2);
第6回読書会で疑問だったこと
● 実行コンテキストがLexEnvとVarEnvという2つの
LexicalEnvironmentを保持しているのはなぜなのか?○ 10.3.1によると、識別子の解決はLexEnvだけを使っている。
○ じゃあVarEnvって意味あるの?
第6回読書会で疑問だったこと
● VarEnvは何に使われている?○ 宣言的バインディングのインスタンス化(10.5)に使われる。
● つまり、実行コンテキスト生成後、実行コンテキストが参照
するVarEnvが保持するEnvironmentRecordに対して、バイ
ンディングの設定を行っている。
→ バインディングの設定時にはVarEnvを使って、解決時に
はLexEnvを使っているらしい。
○ p94に実行コンテキスト生成時、LexEnvとVarEnvは同じ値だと書いてあ
る。
○ じゃあ、LexEnvだけでよくない?分ける意味あるの?
第6回読書会で疑問だったこと
● わざわざ分けているということは、LexEnvとVarEnvが別々
の値を指すことがあるはず。
○ with文(12.10 p152)とcatch節(12.14 p157)の評価時にLexEnvの値が変
わる。
● でも、VarEnvって実行コンテキスト生成直後にコード中の変
数や関数のバインディングに使った後は、特に何にも使わ
れていない様子。
○ 実行コンテキス生成時点でLexEnvとVarEnvが違う値を指していることがな
い限り、LexEnvとVarEnvの2つのLexicalEnvironmentを保持している意
味がないのでは?
第6回読書会で疑問だったこと
● 10.4.2 evalコードの実行(p95-p96)によると、非strict環境
下でDirect evalすると、LexEnvとVarEnvはevalした時点の
実行コンテキストのLexEnvとVarEnvを引き継いだ実行コン
テキストが作成される。
→ ということは、LexEnvとVarEnvがあるのは、LexEnvとVarEnvが異なる状態でDirect evalした時のため?
● 例えば下記のようなコードを考えてみる。
第6回読書会で疑問だったこと
(function() { with({}) { var a = 1; } console.log(a);})();
第6回読書会で疑問だったこと(function() { // ここ with({}) { var a = 1; } console.log(a);})();
第6回読書会で疑問だったこと(function() { with({}) { // ここ var a = 1; } console.log(a);})();
第6回読書会で疑問だったこと(function() { with({}) { var a = 1; // ここ } console.log(a);})();
第6回読書会で疑問だったこと(function() { with({}) { var a = 1; } // ここ console.log(a);})();
● Direct evalで同じ処理をする場合、
第6回読書会で疑問だったこと
(function() { with({}) { eval(“var a = 1”); } console.log(a);})();
第6回読書会で疑問だったこと(function() { // ここ with({}) { eval(“var a = 1”); } console.log(a);})();
第6回読書会で疑問だったこと(function() { with({}) { // ここ eval(“var a = 1”); } console.log(a);})();
第6回読書会で疑問だったこと(function() { with({}) { eval(“var a = 1”); // ここ(evalに入った直後) } console.log(a);})();
第6回読書会で疑問だったこと(function() { with({}) { eval(“var a = 1”); // ここ(evalが終わった時点) } console.log(a);})();
第6回読書会で疑問だったこと(function() { with({}) { eval(“var a = 1”); // ここ } console.log(a);})();
第6回読書会で疑問だったこと(function() { with({}) { eval(“var a = 1”); } // ここ console.log(a);})();
第6回読書会で疑問だったこと
● つまり、非strictの状態で、withやcatch内でDirect evalした
際に、evalしない場合と同じように外側の実行コンテキスト
のLexEnvに変数をバインドをする、という挙動をさせるため
にLexEnvとVarEnvを保持しているように思える。
● 下位互換性のためにこのような仕様になっている...?