ES.next Draft仕様より、普通のfunctionとArrowFunctionの違い
通常のFunciton定義と、ArrowFunctionでの違いを追ってみる。
以下の3つくらいの理解に必要だろう。
Functionオブジェクトの違い(FunctionExpressionとArrowFunction)
まずは、Functionオブジェクトがどのように生成されるか、である。
Function定義は、FunctionDeclarationと名前付きFunctionExpression、名前なしFunctionExpressionで処理が微妙に違うけど、名前なしFunctionExpressionが比較しやすいので、それを抜粋する
13.1 FunctionExpression | 13.2 ArrowFunction |
---|---|
|
|
違いは
13.6 Functionオブジェクトとコンストラクタの作成
上記処理中に出てくる、FunctionCreate
とMakeConstructor
について
FunctionCreate
FunctionCreate (kind, parameterList, body, scope, strict)
引数:
- kind: Normal, Method, Arrow のどれか
- parameterList: 関数の引数リスト
- body: 関数の中身
- scope: LexicalEnvironment
- strict: Strictモードかどうかの真偽値
ステップ数が20ほどあり、長いので一部を抜粋
- if (kind === Arrow) F.[[ThisMode]] = lexical
- else if (strict === true) F.[[ThisMode]] = strict
- else F.[[ThisMode]] = global
MakeConstructor
MakeConstructor (F, writablePrototype, prototype)
引数:
- F: Functionオブジェクト
- writablePrototype: 真偽値
- prototype: プロトタイプとなるオブジェクト
- installNeeded =
false
- if (! prototype )
- installNeeded =
true
- protoype = ObjectCreate()
- installNeeded =
- if (! writablePrototype )
- writablePrototype =
true
- writablePrototype =
- F に [[Construct]] 内部メソッドを設定
- if ( installNeeded )
- prototype に "constructor" プロパティとして F を設定
- F に "prototype" プロパティとして prototype を設定
- return
要するに、コンストラクタとして機能するため(new
できるよう)に必要な[[Construct]]内部メソッドと"prototype"プロパティの設定をしている。このMakeConstructorはArrowFunction時には呼ばれないため、ArrowFunctionには"prototype"プロパティすらないことになる。
8.3.15.1 [[Call]] Internal Method
実際に関数をコールしたときの処理を追ってみる
[[Call]] (thisArgument, argumentList)
引数:
- thisArgument: thisとなるオブジェクト(プリミティブ値もありえるけど)
- argumentsList: 引数リスト
ここも一部抜粋
- thisMode = F.[[ThisMode]]
- if ( thisMode === lexcial )
- localEnv = new NewDeclarativeEnvironment(F.[[Scope]])
- else
- if ( thisMode === strict ) thisValue = thisArgument
- else
- if ( thisArgument === null || undefined )
- thisValue = グローバルオブジェクト
- else if ( thisArgument is not object ) thisValue = ToObject(thisArgument)
- else thisValue = thisArgument
- if ( thisArgument === null || undefined )
- localEnv = new NewFunctionEnvironment(F, thisValue)
最初のF.[[ThisMode]]
はCreateFunction時に設定され
- ArrowFunction: lexical
- FunctionExpression: Strictモードなら strict, そうでないなら global
であった。
よって、ArrowFunctionでは NewDeclarativeEnvironment でlocalEnvを作り、他は NewFunctionEnvironment で作っていることが分かる。
10.2.2.4 NewFunctionEnvironment | 10.2.2.2 NewDeclarativeEnvironment |
---|---|
|
|
NewFunctionEnvironment
Step.3 で新たな変数の環境(所謂、関数スコープ?)を作って、Step.4 でthisValueを設定していることに注目、かな。
NewDeclarativeEnvironment
渡されてくるEはNewDeclarativeEnvironment(F.[[Scope]])
であり、これは関数定義時に渡される現LexicalEnvironmentである。
Step.2で新たな変数の環境を作っているのは変らないが、NewFunctionEnvironmentのStep.4のthisValueの設定はない。関数定義時に渡される現LexicalEnvironmentをそのまま使用することになる。
これが、
var f = () => this.name; var name = "global"; var obj = { name: "obj" }; f.call(obj); // => "global"
とcallメソッドからthisValueを設定しても、[[Call]]で無視される仕組みが分かるし、そして、
var bound = f.bind(obj); bound(); // => "global" ???
とbindメソッドを使用した場合も、8.4.1.1の[[Call]]が使用され、thisValueが設定されるが、元の8.3.15.1 [[Call]] が呼ばれるため、結局thisValueは無視される、と。
はぁ〜、やっとたどり着いた。