web-dev-qa-db-ja.com

ES6のブロックレベル関数の正確なセマンティクスは何ですか?

生の仕様を読んで、ES6の新しい標準化されたブロックレベルの関数に頭を悩ませようとしています。私の表面的な理解は次のとおりです。

  • ES6では、ブロックレベルの関数宣言が許可されています。
  • 彼らはブロックの一番上に持ち上げます。
  • 厳密モードでは、それらは包含ブロックの外側には表示されません。

ただし、これは、これらのセマンティクスの一部が「オプション」であり、Webブラウザーにのみ必須であると指定されているという事実によってさらに複雑になります( 付録B )。だから私は次の表に記入してもらいたい:

 |ブロックの外側に表示されますか? |巻き上げ?どの時点まで? | 「TDZ」? | 
 -------------------------------------------- -------------------------------------------------- -------------------------- 
 |非厳密モード、「Web拡張機能」なし| | | | 
 |厳密モード、「Web拡張機能」なし| | | | 
 |非厳密モード、「Web拡張機能| | | | 
 |厳密モード、「Web拡張機能」| | | | 

また、この文脈で「厳密モード」が何を意味するのかは私にはわかりません。この区別は、関数宣言の実行時実行のためのいくつかの追加手順の一部として、 付録B3. で導入されたようです。

1. If strict is false, then
...

ただし、私が見る限り、strictは関数オブジェクトの[[Strict]]内部スロットを指します。これは次のことを意味しますか?

// Non-strict surrounding code

{
    function foo() {"use strict";}
}

上記の表で「厳密モード」と見なす必要がありますか?しかし、それは私の最初の直感と矛盾しています。

実際の実装の不整合に関係なく、私は主にES6仕様自体に関心があることを覚えておいてください。

20
rvidal

私が見る限り、strictは関数オブジェクトの[[Strict]]内部スロットを指します。

いいえ、はい。これは、関数の厳密さ( またはスクリプトを参照します。ここで関数宣言を含むブロックが発生します。宣言される(または宣言されない)関数の厳密さではありません。

「Web拡張機能」は、ずさんな(厳密ではない)コードにのみ適用され、関数ステートメントの外観が「正気」である場合にのみ適用されます。たとえば、名前が正式なパラメーターと衝突しない場合や字句的に宣言された変数。

Web互換性のセマンティクスがなければ、厳密なコードとずさんなコードの間に違いはないことに注意してください。純粋なES6では、ブロック内の関数宣言の動作は1つだけです。

だから私たちは基本的に持っています

                 |      web-compat               pure
-----------------+---------------------------------------------
strict mode ES6  |  block hoisting            block hoisting
sloppy mode ES6  |  it's complicated ¹        block hoisting
strict mode ES5  |  undefined behavior ²      SyntaxError
sloppy mode ES5  |  undefined behavior ³      SyntaxError

1:以下を参照してください。警告が求められます。
2:通常、SyntaxErrorがスローされます
3: ES5.1§12 の注記は、「実装間の重要で調整不可能なバリエーション」(たとえば- これら )。警告をお勧めします。

では、Web互換性を備えたES6実装は、レガシーセマンティクスを備えたずさんなモード関数のブロック内の関数宣言に対してどのように動作するのでしょうか。
まず第一に、純粋なセマンティクスが引き続き適用されます。つまり、関数宣言は字句ブロックの先頭に引き上げられます。
ただし、囲んでいる関数の先頭に持ち上げられるvar宣言もあります。
そして、関数宣言が評価されると(ブロック内で、ステートメントのように満たされたかのように)、関数オブジェクトが割り当てられますその関数スコープの変数に。

これはコードによってよりよく説明されます:

function enclosing(…) {
    …
    {
         …
         function compat(…) { … }
         …
    }
    …
}

と同じように動作します

function enclosing(…) {
    var compat₀ = undefined; // function-scoped
    …
    {
         let compat₁ = function compat(…) { … }; // block-scoped
         …
         compat₀ = compat₁;
         …
    }
    …
}

はい、それは少し混乱します。同じ名前の2つの異なるバインディング(下付き文字0と1で示されます)があります。だから今、私はあなたの質問に簡潔に答えることができます:

ブロックの外側に表示されますか?

はい、varのように。ただし、ブロック内でのみ表示される2番目のバインディングがあります。

巻き上げ?

はい-2回。

どの時点まで?

関数(ただし、undefinedで初期化)とブロック(関数オブジェクトで初期化)の両方。

「TDZ」?

参照をスローする字句的に宣言された変数(let/const/class)の一時的なデッドゾーンの意味ではありません。ただし、本体の実行で関数宣言が検出される前は、関数スコープの変数はundefined(特にブロックの前)であり、これを呼び出そうとすると例外も発生します。


参考までに:ES6では、上記の動作は関数スコープ内のブロックに対してのみ指定されていました。 ES7以降 同じことがevalおよびグローバルスコープのブロックにも当てはまります。

26
Bergi

あなたの混乱がどこから来ているのかわかりません。 10.2.1 によると、「厳密モード」であるかどうかは非常に明確です。サンプルでは、​​foos [[Strict]]内部スロットは確かにtrueであり、厳密モードになりますが、それをホストするブロックはそうではありません。最初の文(引用したもの)は、ホスティングブロックに関連しており、その中で生成されたコンテンツには関連していません。フラグメント内のブロックは厳密モードではないため、そのセクションが適用されます。

1
Amit