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
oldEnv
be the running execution context’s LexicalEnvironment.- Let
blockEnv
beNewDeclarativeEnvironment(oldEnv)
.- Perform
BlockDeclarationInstantiation(StatementList, blockEnv)
.- Set the running execution context’s LexicalEnvironment to
blockEnv
.- Let
blockValue
be 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
envRec
be env’s EnvironmentRecord.- Assert:
envRec
is a declarative Environment Record.- Let
declarations
be the LexicallyScopedDeclarations of code.- For each element
d
in declarations, do For each elementdn
of 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 Letfn
be the sole element of the BoundNames of d. Letfo
be InstantiateFunctionObject ofd
with 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();