What Are the Precise Semantics of Block-Level Functions in Es6

What are the precise semantics of block-level functions in ES6?

As far as I can see, strict refers to the [[Strict]] internal slot of the function object.

No. And yes. It does refer to the strictness of the function (or script) in which the block that contains the function declaration occurs. Not to the strictness of the function that is (or is not) to be declared.

The "web extensions" do only apply to sloppy (non-strict) code, and only if the appearance of the function statement is "sane" - that is, for example, if its name doesn't collide with a formal parameter or lexically declared variable.

Notice that there is no difference between strict and sloppy code without the web-compatibility semantics. In pure ES6, there is only one behaviour for function declarations in blocks.

So we basically have

                 |      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: See below. Warnings are asked for.

2: Typically, a SyntaxError is thrown

3: The note in ES5.1 §12 talks of "significant and irreconcilable variations among the implementations" (such as these). Warnings are recommended.

So now how does an ES6 implementation with web compatibility behave for a function declaration in a block in a sloppy-mode function with legacy semantics?

First of all, the pure semantics still apply. That is, the function declaration is hoisted to the top of the lexical block.

However, there is also a var declaration that is hoisted to the top of the enclosing function.

And when the function declaration is evaluated (in the block, as if it was met like a statement), the function object is assigned to that function-scoped variable.

This is better explained by code:

function enclosing(…) {

{

function compat(…) { … }

}

}

works the same as

function enclosing(…) {
var compat₀ = undefined; // function-scoped

{
let compat₁ = function compat(…) { … }; // block-scoped

compat₀ = compat₁;

}

}

Yes, that's a bit confusing, having two different bindings (denoted with the subscripts 0 and 1) with the same name. So now I can succinctly answer your questions:

Visible outside of block?

Yes, like a var. However, there's a second binding that is visible only inside the block.

Hoisted?

Yes - twice.

Up to which point?

Both to the function (however initialised with undefined) and the block (initialised with the function object).

"TDZ"?

Not in the sense of the temporal dead zone of a lexically declared variable (let/const/class) that throws on referencing, no. But before the function declaration is encountered in the execution of the body, the function-scoped variable is undefined (especially before the block), and you'll get an exception as well if you try to call it.


Just for reference: in ES6, the above-described behaviour was specified only for blocks in function scopes. Since ES7 the same applies to blocks in eval and global scopes.

In ES6 what is new spec, block-level function declaration mean?

the example kangax is testing for:

alert(function(){
'use strict';
function f() { return 1; }
{
function f() { return 2; }
}
return f() === 1;
}());

it means that function "hoisting" behaves the same way as let (vs var).

In ES5, braces were "decoration", unless they appeared after a few keywords like for, if, try, etc. so, the 2nd f() would "clobber" the 1st, but in ES6-compat runtimes, the 2nd f() is private to the block and thus doesn't replace the name f defined by the 1st function.

In ES6 braces ({ ... }) mean a block, even without a preceding keyword. That said, i don't see many arbitrary blocks in ES6 code, maybe just from lack of practice, ignorance, or perhaps just from lack of need; function scope works pretty well in JS.

Why is a block-scoped function work outside the scope?

As Kyle's book mentions, the semantics of function declarations in blocks has in the past varied between implementations. (That may explain why declaring functions inside blocks was generally discouraged in coding style commentaries.)

Browsers generally want as much old code to work as possible, so with a fairly important change like not hoisting a block-nested function declaration to the containing function scope old code could certainly break. Thus, for lack of a better alternative, the "use strict"; flag turns the ES6 semantics "on". Old code (probably) won't have "use strict"; anywhere, so it'll continue to behave the old way (which of course has always been risky).

what is block scope function ECMAScript 6 compare with ECMAScript 5

The new let and const in ES2015 (aka "ES6") have four major differences compared with the venerable var:

  1. They have block scope

  2. They aren't hoisted (well, they're sort of hoisted, but in a useful way)

  3. Repeated declarations are errors

  4. When used at global scope, they don't create properties of the global object (despite creating global variables; this is a new concept as of ES2015)

(For what it's worth, this is covered in detail in Chapter 2 of my recent book JavaScript: The New Toys, which covers ES2015-ES2020.)

Block scope

var variables exist throughout the function they're declared in (or globally, if declared globally), they aren't confined to the block they're in. So this code is valid:

function foo(flag) {
a = 10;
if (flag) {
var a = 20;
}
return a;
}
console.log(foo(false)); // 10
console.log(foo(true)); // 20

How does this hoisting work with block scope?


According to the web compat semantics at the place of the function declaration, the value of the blocked scope variable is bound to the outer scope². This code is equivalent to:

let outerFoo; // the functions create a binding outside of the scope

{
let innerFoo; // but also inside
// due to hoisting, functions get bound before any code get's executed:
innerFoo = function foo() {
console.log('A');
};
innerFoo = function foo() {
console.log('B');
};

// At the place of the function declaration, the variable leaves the scope
/* function foo() {
console.log('A');
} */
outerFoo = innerFoo;

innerFoo();

innerFoo = 1;

// this also applies to the second declaration
/* function foo() {
console.log('B');
} */
outerFoo = innerFoo;

innerFoo = 2;

console.log(innerFoo);
}
console.log(outerFoo);


Related Topics



Leave a reply



Submit