Quais são as semânticas precisas das funções em nível de bloco no ES6?

Eu estou tentando enrolar minha cabeça em torno das novas funções padronizadas em nível de bloco no ES6 lendo as especificações brutas. Meu entendimento superficial foi:

  • Declarações de funções em nível de bloco são permitidas no ES6.
  • Eles chegam ao topo do bloco.
  • No modo estrito, eles não são visíveis fora do bloco de contenção.

No entanto, isso é ainda mais complicado pelo fato de que parte dessas semânticas são especificadas como “opcionais” e obrigatórias apenas para navegadores da web ( Anexo B ). Então eu gostaria de ter a seguinte tabela preenchida:

                                              |  Visível fora do bloco?  |  Içado?  Até que ponto?  |  "TDZ"?  |  -------------------------------------------------- -------------------------------------------------- -------------------- |  Modo não estrito, sem "extensões da web" |  |  |  |  |  Modo estrito, sem "extensões web" |  |  |  |  |  Modo não estrito, com "extensões da Web | | | | | Modo estrito, com" extensões da Web "| | | | 

Também não está claro para mim o que significa “modo estrito” neste contexto. Essa distinção parece ser introduzida no Anexo B3.3 , como parte de algumas etapas adicionais para a execução em tempo de execução de uma declaração de function:

1. If strict is false, then ... 

No entanto, tanto quanto eu posso ver, strict refere-se ao slot interno [[Strict]] do object de function. Isso significa que:

 // Non-strict surrounding code { function foo() {"use strict";} } 

deve ser considerado “modo estrito” na tabela acima? No entanto, isso contradiz minha intuição inicial.

Por favor, tenha em mente que estou mais interessado na própria especificação do ES6, independentemente das inconsistências reais de implementação.

Tanto quanto eu posso ver, strict refere-se ao slot interno [[Strict]] do object de function.

Não e sim. Ele se refere ao rigor da function ( ou script ) no qual o bloco que contém a declaração de function ocorre. Não ao rigor da function que é (ou não) ser declarada.

As “extensões da Web” só se aplicam a códigos desleixados (não restritos) e somente se a aparência da instrução de function for “sã” – isto é, por exemplo, se seu nome não colidir com um parâmetro formal ou lexicamente variável declarada.

Observe que não há diferença entre código estrito e desleixado sem a semântica de compatibilidade da web. No ES6 puro, existe apenas um comportamento para declarações de function em blocos.

Então nós basicamente temos

  | 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: Veja abaixo. Advertências são solicitadas.
2: Normalmente, um SyntaxError é lançado
3: A nota no ES5.1 §12 fala de ” variações significativas e irreconciliáveis ​​entre as implementações ” (como estas ). Avisos são recomendados.

Então, como uma implementação ES6 com compatibilidade com a Web se comporta para uma declaração de function em um bloco em uma function de modo desleixado com semântica herdada?
Primeiro de tudo, a semântica pura ainda se aplica . Ou seja, a declaração da function é içada para o topo do bloco léxico.
No entanto, há também uma declaração var que é içada para o topo da function de inclusão.
E quando a declaração de function é avaliada (no bloco, como se fosse atendida como uma instrução), o object de function é atribuído a essa variável com escopo de function.

Isso é melhor explicado pelo código:

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

funciona da mesma

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

Sim, isso é um pouco confuso, tendo duas ligações diferentes (denotadas com os subscritos 0 e 1) com o mesmo nome. Então agora posso responder de forma sucinta às suas perguntas:

Visível fora do bloco?

Sim, como um var . No entanto, há uma segunda binding visível apenas dentro do bloco.

Içado?

Sim, duas vezes.

Até que ponto?

Tanto para a function (no entanto inicializado com undefined ) quanto para o bloco (inicializado com o object de function).

“TDZ”?

Não no sentido da zona morta temporal de uma variável declarada lexicalmente ( let / const / class ) que lança na referência, no. Mas antes que a declaração de function seja encontrada na execução do corpo, a variável com escopo de function é undefined (especialmente antes do bloco), e você obterá uma exceção também se tentar chamá-la.

Não tenho certeza de onde vem sua confusão. De acordo com 10.2.1 , é muito claro o que é ou não está “no modo estrito”. Em sua amostra, o slot interno foo s [[Strict]] seria de fato true e estará no modo estrito, mas o bloco que o hospeda não será. A primeira frase (aquela que você citou) está relacionada ao bloco de hospedagem, não ao conteúdo gerado dentro dela. O bloco no seu fragment não está no modo estrito e, portanto, essa seção se aplica a ele.