4

最近、名前付き関数式 (NFE) に関する興味深い事実に出くわしました。関数本体内で NFE の関数名にアクセスできることを理解しています。これにより、再帰がより便利になり、節約できarguments.calleeます。また、関数名は関数本体の外では使用できません。例えば、

var foo = function bar() {
    console.log(typeof bar);
}; 

typeof foo; // 'function'
typeof bar; // 'undefined', inaccessible outside the NFE
foo(); // 'function', accessible inside the NFE

これは十分に文書化された機能であり、kangax は NFE に関する素晴らしい投稿をしており、そこでこの現象について言及しています。私が最も驚いたのは、NFE の関数名を関数本体の他の値と再関連付けできないことです。例えば、

(function foo() {
    foo = 5;
    alert(foo);
})(); // will alert function code instead of 5

foo上記の例では、識別子を別の値で再バインドしようとしました5。しかし、これは失敗します!そして、ES5 仕様に目を向けると、NFE の作成時に不変のバインディング レコードが作成され、レキシカル環境の環境レコードに追加されていることがわかりました。

問題は、NFE が関数本体内で独自の関数名を参照するときに、名前が自由変数として解決されることです。上記の例でfooは、 は NFE 内で参照されていますが、この関数の仮パラメーターでもローカル変数でもありません。したがって、これは自由変数であり、そのバインド レコードは NFE の [[scope]] プロパティを通じて解決できます。

したがって、これを考慮してください。外側のスコープに同じ名前の別の識別子がある場合、競合が発生しているようです。例えば、

var foo = 1;
(function foo() {
    alert(foo);
})(); // will alert function code rather than 1
alert(foo); // 1

NFE を実行すると、自由変数 fooは関連付けられている関数に解決されました。しかし、コントロールが NFE コンテキストを終了するfooと、外側のスコープでローカル変数として解決されました。

だから私の質問は次のとおりです。

  1. 関数名の不変バインディング レコードはどこに保存されますか?
  2. fooNFEvar foo = 1内で解決されると、関数名が優先されるのはなぜですか? それらの結合レコードは同じ字句環境に保管されていますか? もしそうなら、どのように?
  3. foo関数名は内部ではアクセスできるが、外部では見えないという現象の背後にあるものは何ですか?

誰かがES5仕様でこれに光を当てることができますか? オンラインでの議論はあまり見当たりません。

4

1 に答える 1

2

関数名の不変バインディング レコードはどこに保存されますか?

あなたが見ることができない余分なレキシカル環境レコードで:-)

fooNFEvar foo = 1内で解決されると、関数名が優先されるのはなぜですか?

実際にはそうではありません。関数スコープ内で衝突なしで新しいローカルを宣言できますが、そうしないと、自由変数は不変バインディングに解決されます。ただし、スコープチェーンの上位にあるグローバル変数よりも重要です。var foofoofoo

var foo = 1;
(function foo() { "use strict";
    var foo = 2;
    console.log(foo); // 2
}());
(function foo() { "use strict";
    console.log(foo); // function …
    foo = 2; // Error: Invalid assignment in strict mode
}());

それらの結合レコードは同じ字句環境に保管されていますか?

いいえ。すべての名前付き関数式は、関数で初期化された関数の名前に対する単一の不変バインディングを持つ特別な字句環境に囲まれています。

これは、仕様の関数定義 (§13)セクションで説明されています。関数宣言と無名関数式の手順は基本的に「現在の実行コンテキストのスコープのレキシカル環境を使用して、その関数本体で新しい関数オブジェクトを作成する」ですが、名前付き関数式はより複雑です。

  1. funcEnvNewDeclarativeEnvironment実行中の実行コンテキストのレキシカル環境を引数として渡して呼び出し結果とする
  2. の環境記録にしましょうenvRecfuncEnv
  3. 関数の を引数として渡すCreateImmutableBinding(N)具体的なメソッドを呼び出します。envRecIdentifier
  4. 新しいFunctionclosureオブジェクトを作成した結果とする […]. Scopeとして渡します。funcEnv
  5. 関数の とを引数として渡すInitializeImmutableBinding(N,V)具体的なメソッドを呼び出します。envRecIdentifierclosure
  6. 戻るclosure

関数式のためだけに追加のラッパー環境を構築します。ブロック スコープを使用した ES6 コードでは、次のようになります。

var x = function foo(){};
// is equivalent to
var x;
{
    const foo = function() {};
    x = foo;
}
// foo is not in scope here

foo関数名は内部ではアクセスできるが、外部では見えないという現象の背後にあるものは何ですか?

不変バインディングは、現在のfoo実行コンテキストの字句環境では作成されませんが、関数式の周りのクロージャーにのみ使用されるラッパー環境で作成されます。

于 2015-05-30T15:20:16.403 に答える