ES.next Draft仕様より、普通のfunctionとArrowFunctionの違い

の解説を理解するために。

通常のFunciton定義と、ArrowFunctionでの違いを追ってみる。
以下の3つくらいの理解に必要だろう。

  • 8.3.15.1 [[Call]] Internal Method
  • 10.2 Lexical Environments
  • 13 Function and Generators
    • 13.1 Function Definitions
    • 13.2 Arrow Function Definitions
    • 13.6 Creating Function Objects and Constructors

Functionオブジェクトの違い(FunctionExpressionとArrowFunction)

まずは、Functionオブジェクトがどのように生成されるか、である。

Function定義は、FunctionDeclarationと名前付きFunctionExpression、名前なしFunctionExpressionで処理が微妙に違うけど、名前なしFunctionExpressionが比較しやすいので、それを抜粋する

13.1 FunctionExpression 13.2 ArrowFunction
  1. strict = Strictモード中 || FunctionBodyがStrictモード ? true : false
  2. scope = 現LexicalEnvironment
  3. closure = FunctionCreate(Normal, FormalParameterList(引数), FunctionBody, scope, strict)
  4. MakeConstructor(closure)
  5. closure を返す
  1. strict = true
  2. scope = 現LexicalEnvironment
  3. closure = FunctionCreate(Arrow, ArrowParameters(引数), ConciseBody, scope, strict)
  4. closure を返す

違いは

  • Strictモードであるか否かが、FunctionExpression側では変わるが、ArrowFunctionでは固定でStrictモード
  • FunctionCreate抽象演算で Normal として呼ばれるか、Arrow として呼ばれるか
  • MakeConstructor抽象演算はArrowFunctionでは呼ばれない
    • これは、ArrowFunctionはコンストラクタとして機能(new)できないことを意味する
    • 後々実装されてくるであろうMethodDefinitionも同様にコンストラクタとして機能できない

13.6 Functionオブジェクトとコンストラクタの作成

上記処理中に出てくる、FunctionCreateMakeConstructorについて

FunctionCreate
FunctionCreate (kind, parameterList, body, scope, strict)

引数:

  • kind: Normal, Method, Arrow のどれか
  • parameterList: 関数の引数リスト
  • body: 関数の中身
  • scope: LexicalEnvironment
  • strict: Strictモードかどうかの真偽値

ステップ数が20ほどあり、長いので一部を抜粋

  1. if (kind === Arrow) F.[[ThisMode]] = lexical
  2. else if (strict === true) F.[[ThisMode]] = strict
  3. else F.[[ThisMode]] = global
MakeConstructor
MakeConstructor (F, writablePrototype, prototype)

引数:

  • F: Functionオブジェクト
  • writablePrototype: 真偽値
  • prototype: プロトタイプとなるオブジェクト
  1. installNeeded = false
  2. if (! prototype )
    • installNeeded = true
    • protoype = ObjectCreate()
  3. if (! writablePrototype )
    • writablePrototype = true
  4. F に [[Construct]] 内部メソッドを設定
  5. if ( installNeeded )
    • prototype に "constructor" プロパティとして F を設定
  6. F に "prototype" プロパティとして prototype を設定
  7. return

要するに、コンストラクタとして機能するため(newできるよう)に必要な[[Construct]]内部メソッドと"prototype"プロパティの設定をしている。このMakeConstructorはArrowFunction時には呼ばれないため、ArrowFunctionには"prototype"プロパティすらないことになる。

8.3.15.1 [[Call]] Internal Method

実際に関数をコールしたときの処理を追ってみる

[[Call]] (thisArgument, argumentList)

引数:

  • thisArgument: thisとなるオブジェクト(プリミティブ値もありえるけど)
  • argumentsList: 引数リスト

ここも一部抜粋

  1. thisMode = F.[[ThisMode]]
  2. if ( thisMode === lexcial )
    • localEnv = new NewDeclarativeEnvironment(F.[[Scope]])
  3. else
    • if ( thisMode === strict ) thisValue = thisArgument
    • else
      1. if ( thisArgument === null || undefined )
        • thisValue = グローバルオブジェクト
      2. else if ( thisArgument is not object ) thisValue = ToObject(thisArgument)
      3. else thisValue = thisArgument
    • 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
  1. Assert: FthisModeが lexical でないこと
  2. env = 新規 lexical environment
  3. envRec = 新規 declarative environment (空のバインディング)
  4. envRecthisValueT を設定
  5. 省略
  6. 省略
  7. env.environment = envRec
  8. env の外部lexical environmentへの参照として Fの[[Scope]] を設定
  9. env を返す
  1. env = 新規 lexical environment
  2. envRec = 新規 declarative environment (空のバインディング)
  3. env.environment = envRec
  4. env の外部lexical environmentへの参照として E を設定
  5. env を返す
NewFunctionEnvironment

Step.3 で新たな変数の環境(所謂、関数スコープ?)を作って、Step.4 でthisValueを設定していることに注目、かな。

NewDeclarativeEnvironment

渡されてくるENewDeclarativeEnvironment(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は無視される、と。

はぁ〜、やっとたどり着いた。