There are four kinds of context in which variable or function declarations would occur: Global, module, function, and block. This article focuses on the block context.
Steps in Specification of Declarations in Block Context
The specification of Evaluation of blocks and BlockDeclarationInstantiation are not as complicated as the function or global ones. So we can look into the source of specification directly:
14.2.2 Runtime Semantics: Evaluation Block : { StatementList }
- Let
oldEnvbe the running execution context’s LexicalEnvironment.- Let
blockEnvbeNewDeclarativeEnvironment(oldEnv).- Perform
BlockDeclarationInstantiation(StatementList, blockEnv).- Set the running execution context’s LexicalEnvironment to
blockEnv.- Let
blockValuebe the result of evaluating StatementList.- Set the running execution context’s LexicalEnvironment to
oldEnv.- Return
blockValue.
14.2.3 BlockDeclarationInstantiation ( code, env ) The abstract operation BlockDeclarationInstantiation takes arguments code (a Parse Node) and env (a declarative Environment Record) and returns unused. code is the Parse Node corresponding to the body of the block. env is the Environment Record in which bindings are to be created. Note: When a Block or CaseBlock is evaluated a new declarative Environment Record is created and bindings for each block scoped variable, constant, function, or class declared in the block are instantiated in the Environment Record.
- “ Let
envRecbe env’s EnvironmentRecord.- Assert:
envRecis a declarative Environment Record.- Let
declarationsbe the LexicallyScopedDeclarations of code.- For each element
din declarations, do For each elementdnof the BoundNames ofd, do If IsConstantDeclaration of d is true, then Perform !envRec.CreateImmutableBinding(dn, true). Else, Perform !envRec.CreateMutableBinding(dn, false). If d is a FunctionDeclaration, a GeneratorDeclaration, an AsyncFunctionDeclaration, or an AsyncGeneratorDeclaration, then Letfnbe the sole element of the BoundNames of d. Letfobe InstantiateFunctionObject ofdwith argumentenv. PerformenvRec.InitializeBinding(fn, fo).
According to the spec, BlockDeclarationInstantiation does not include var statements (VariableStatement), as these are not part of LexicallyScopedDeclarations. The LexicallyScopedDeclarations include let, const, function, and class.
As mentioned in the previous articles, the VariableStatements in nested blocks have been collected into VarScopedDeclarations when processing global or function declaration instantiations.
And the BlockDeclarationInstantiation in the block are bonded to the newly created declarative Environment Record, whose environment’s outer environment is the running execution context’s LexicalEnvironment
Test in the Hosts
We can test the behavior in actual hosts. The following pieces of code are tested in the hosts with the same result:
- Edge 127.0.2651.86
- Firefox 129.0
- Safari 17.2.1
- Node v18.17.1
function declarations
function declarationsinside the block should NOT be hoisted out of that block:
-
In a global context:
"use strict";foo("before block"); // ReferenceError: foo is not defined{foo("in block before declaration");function foo(position) {console.log("Block scoped function", position);}foo("in block after declaration");}foo("after block"); -
In a function context:
"use strict";function test() {foo("before block"); // ReferenceError: foo is not defined{foo("in block before declaration");function foo(position) {console.log("Block scoped function", position);}foo("in block after declaration");}foo("after block");}test();
var declarations
var declarationsinside the block should be hoisted out of that block and initialized with value undefined:
-
In a global context:
"use strict";console.log("before block", bar); // undefined{console.log("in block before declaration", bar); // undefinedvar bar = 30;console.log("in block after declaration", bar); // 30}console.log("after block", bar); // 30 -
In a function context:
"use strict";function test() {console.log("before block", bar); // undefined{console.log("in block before declaration", bar); // undefinedvar bar = 30;console.log("in block after declaration", bar); // 30}console.log("after block", bar); // 30}test();