It is clear that only let
and const
are block scoped, but in the spec it seems that anytime a block is evaluated in BlockEvaluation a new block scope is created with BlockDeclarationInstantiation
.
- Let oldEnv be the running execution context’s LexicalEnvironment.
- Let blockEnv be NewDeclarativeEnvironment(oldEnv).
- Perform BlockDeclarationInstantiation(StatementList, blockEnv).
- Set the running execution context’s LexicalEnvironment to blockEnv.
- Let blockValue be Completion(Evaluation of StatementList).
- Set the running execution context’s LexicalEnvironment to oldEnv.
- Return ? blockValue.
In BlockDeclarationInstantiation it states:
- Let declarations be the LexicallyScopedDeclarations of code.
In this code when evaluation of the block begins, according to step 2
, a block scope is created, but am I correct in assuming that during the evaluation of the block, a new block scope is created, no var
variables are registered for the environment record and the scope is thrown out, or since the AST is created in the compilation phase, during execution does the js engine already know ahead of time not to waste time creating a block since the AST shows it contains only a var declaration?
I would expect that there is first a check to see if there are any LexicallyScopedDeclarations before the AO NewDeclarativeEnvironment is ran at all.
function foo() {
if (true) {
var x = 1
}
}
In the AST explorer it generates the following json showing a block statement is created with a variable statement within it. It seems during evaluation stage of the js engine, it creates this block scope and realizes there is a var
declaration and does not register it to that scope.
{
"type": "Program",
"start": 0,
"end": 50,
"body": [
{
"type": "FunctionDeclaration",
"start": 0,
"end": 50,
"id": {
"type": "Identifier",
"start": 9,
"end": 12,
"name": "foo"
},
"expression": false,
"generator": false,
"async": false,
"params": [],
"body": {
"type": "BlockStatement",
"start": 15,
"end": 50,
"body": [
{
"type": "IfStatement",
"start": 19,
"end": 48,
"test": {
"type": "Literal",
"start": 23,
"end": 27,
"value": true,
"raw": "true"
},
"consequent": {
"type": "BlockStatement",
"start": 29,
"end": 48,
"body": [
{
"type": "VariableDeclaration",
"start": 35,
"end": 44,
"declarations": [
{
"type": "VariableDeclarator",
"start": 39,
"end": 44,
"id": {
"type": "Identifier",
"start": 39,
"end": 40,
"name": "x"
},
"init": {
"type": "Literal",
"start": 43,
"end": 44,
"value": 1,
"raw": "1"
}
}
],
"kind": "var"
}
]
},
"alternate": null
}
]
}
}
],
"sourceType": "module"
}
2
Answers
The spec details only the behaviour of a javascript program, not the specific steps an engine will execute (or optimise away) when running a script. Whether empty unnecessary scopes will be created and used, created and immediately thrown away, or not created at all, is an implementation detail and will not affect the observable behaviour. At best, your debugger might be able to give you a glimpse at it.
It will likely depend on each engine. Useless work that has no effect on the execution of the code is typically optimised away, or even not generated in the first place. For example, though
+
has a very specific definition in the spec, I would expectconsole.log(2+4)
to be compiled into the same code asconsole.log(6)
, though if the engine was executing the spec literally, it should not do that.Here it specifically says:
These types and the behaviour that the specification expects from them is the simplest way to explain how JavaScript code should behave; but it is not there to suggest how an engine should work internally. Any of these explanations can and will be ignored if it results in a better execution, while preserving the outwardly visible semantics of the engine viewed as a black box.