Take let variable out of temporal dead zone - javascript

See this code:
<script>
let {foo} = null; // TypeError
</script>
<script>
// Here I want to assign some some value to foo
</script>
The first script attempts to let-declare foo via a destructuring assignment. However, null can't be destructured, so the assignment throws a TypeError.
The problem is that then the foo variable is declared but uninitialized, so if in the 2nd script I attempt to reference foo, it throws:
foo = 123; // ReferenceError: can't access lexical declaration `foo' before initialization
And let variables can't be redeclared:
let foo = 123; // SyntaxError: redeclaration of let foo
Is there any way to take it out of the TDZ, so that I can assign values and read them?

It's impossible. Temporal dead zone and restricted access to uninitialized let variable are expected to be unavoidaible. It's confusing and problematic, but intended and expected.
See spec for details:
NOTE let and const declarations define variables that are scoped to the running execution context’s LexicalEnvironment. The variables are created when their containing Lexical Environment is instantiated but may not be accessed in any way until the variable’s LexicalBinding is evaluated. A variable defined by a LexicalBinding with an Initializer is assigned the value of its Initializer’s AssignmentExpression when the LexicalBinding is evaluated, not when the variable is created. If a LexicalBinding in a let declaration does not have an Initializer the variable is assigned the value undefined when the LexicalBinding is evaluated.\
So if variable wasn't initialized on declaration (and throwing before initialization obviously result in no initialization) it can't be accessed by any means.
But in fact, your issue is more complex than throwing assigment. It's architecture issue - you are depending on mutable global variables. It's big "no no no" and you should refactor your code to use explicit dependencies.

Related

Scope of a let variable JavaScript [duplicate]

In JavaScript, var declarations create properties on the global object:
var x = 15;
console.log(window.x); // logs 15 in browser
console.log(global.x); // logs 15 in Node.js
ES6 introduces lexical scoping with let declarations that have block scope.
let x = 15;
{
let x = 14;
}
console.log(x); // logs 15;
However, do these declarations create properties on the global object?
let x = 15;
// what is this supposed to log in the browser according to ES6?
console.log(window.x); // 15 in Firefox
console.log(global.x); // undefined in Node.js with flag
Do let statements create properties on the global object?
According to the spec, no:
A global environment record is logically a single record but it is specified as a composite encapsulating an object environment record and a declarative environment record. The object environment record has as its base object the global object of the associated Realm. This global object is the value returned by the global environment record’s GetThisBinding concrete method. The object environment record component of a global environment record contains the bindings for all built-in globals (clause 18) and all bindings introduced by a FunctionDeclaration, GeneratorDeclaration, or VariableStatement contained in global code. The bindings for all other ECMAScript declarations in global code are contained in the declarative environment record component of the global environment record.
Some more explanation:
A declarative environment record stores the bindings in an internal data structure. It's impossible to get a hold of that data structure in any way (think about function scope).
An object environment record uses an actual JS object as data structure. Every property of the object becomes a binding and vice versa. The global environment has an object environment object whose "binding object" is the global object. Another example is with.
Now, as the cited part states, only FunctionDeclarations, GeneratorDeclarations, and VariableStatements create bindings in the global environment's object environment record. I.e. only this bindings become properties of the global object.
All other declarations (e.g. const and let) are stored in the global environment's declarative environment record, which is not based on the global object.
Standard scripts:
Both let and var variables, if declared at the top-level of a script, are accessible outside of the script file. However, only var variables get assigned to the window object. Have a look at this code snippet as proof:
<script>
var namedWithVar = "with var";
let namedWithLet = "with let";
</script>
<script>
console.log("Accessed directly:");
console.log(namedWithVar); // prints: with var
console.log(namedWithLet); // prints: with let
console.log("");
console.log("Accessed through window:");
console.log(window.namedWithVar); // prints: with var
console.log(window.namedWithLet); // prints: undefined
</script>
Javascipt modules:
Note that modules are a different story. Variables declared in a module are not made available in the global scope:
<script type="module">
var namedWithVar = "with var";
let namedWithLet = "with let";
</script>
<script>
console.log(namedWithVar); // ReferenceError
</script>
<script>
console.log(namedWithLet); // ReferenceError
</script>
<script>
console.log(window.namedWithVar); // prints: undefined
console.log(window.namedWithLet); // prints: undefined
</script>
Per the specification:
"let and const declarations define variables that are scoped to the running execution context’s LexicalEnvironment."
This means that you should be able to access the variable inside the execution scope, but not outside. This expands the execution scope beyond the classic JS closure structure of function-only or global.
Defining a let variable globally should not expose the variable on the global context, as used to be the case in Firefox. In practice you should not define variables in a global context.
Variables declared via let keyword do not create accessible properties on a global object (window for a browser).
Actually, Firefox fixed its behavior: let v = 42; 'v' in window // false
let allows you to declare variables that are limited in scope to the block, statement, or expression on which it is used. This is unlike the var keyword, which defines a variable globally, or locally to an entire function regardless of block scope.
At the top level of programs and functions, let, unlike var, does not create a property on the global object. For example:
var x = 'global';
let y = 'global';
console.log(this.x); // "global"
console.log(this.y); // undefined
The scope of a variable declared with var is its current execution context, which is either the enclosing function or, for variables declared outside any function, global. If you re-declare a JavaScript variable, it will not lose its value. For example:
var x = 1;
if (x === 1) {
var x = 2;
console.log(x);
// output: 2
}
console.log(x);
// output: 2
Note: that unlike C, C++, and Java, JavaScript does not have block-level scope when you declare a variable using var.
As we mentioned before let allows you to declare variables that are limited in scope to the block, statement, or expression on which it is used. For example:
let x = 1;
if (x === 1) {
let x = 2;
console.log(x);
// output: 2
}
console.log(x);
// output: 1
Here I recommend you to read about Variable Scope

Initialization vs assignment

The terms "initialization" and "assignment" seem to be used interchangeably. I did some searching and it seems that there might technically be a difference. My understanding is that, in the context of variables, a variable is initialized when the JavaScript engine makes it available for use, and assignment (whether done explicitly [as in let foo = 1;] or by the JavaScript engine, as in the following example) is one way to achieve this.
let foo;
console.log(foo); // undefined (initialization and assignment?)
Is my understanding correct? Also (if so), what actually occurs during initialization to make the variable available?
TLDR:
{ // declaration (hoisted)
// Temporal deadzone
let foo; // declaration and initialization to undefined
foo = 1; // assignment
}
A bit longer:
Declaration
Declaring a variable means that we reserve the identifier at the current scope. In javascript declarations are hoisted, that means that it gets declared when the scope the variable is in gets visible (the block it is in gets executed). However you cannot access that variable now as it is in
The temporal deadzone
This is a specific part of the code that is between the beginning of the scope and the initialization. Trying to access the variable here results in an error.
Initialization
The initialization takes place in the line were you declared the variable. It will assign a value to the variable and will make it available for access. This for example:
let foo;
will initialize foo to undefined,
let foo = 2;
will initialize foo to 2.
Assignment
...just means that you change the value of a variable. All assignments in javascript use =. The initialization is basically just the first assinment.
The explanation above does not apply to variables declared with var, so just don't use var to avoid confusion :)

Chrome console already declared variables throw undefined reference errors for let

Recently I ran into this weird thing in chrome console. Here I am intentionally assigning an undefined thing to a in order to throw an error.
let a = werwr // Uncaught ReferenceError: werwr is not defined
Then when I tried to assign something legit to a, this happened:
let a = "legit string" // Uncaught SyntaxError: Identifier 'a' has already been declared
so I can't use "let" because a has already been declared. So I tried to reassign something else to the "already declared a"
a = "legit string" // Uncaught ReferenceError: a is not defined
So it seems like I can't reassign something else to a but at the same time, a has been declared so I can't use let again.
I understand the difference between declaring and assigning a variable. However here it seems that neither could be done again. Does this has something to do with the scope of "let" in console? Because the same thing totally works for "var"
var a = werwr
// Uncaught ReferenceError: werwr is not defined
a = ”legit string“
// ”legit string“
var a = "legit string"
// Uncaught SyntaxError: Identifier 'a' has already been declared
Follow-up
There seem to be some difference between "manually" hoisting the let statement vs the implicit case.
throw new Error
let example = 5
// same errors as before
while in this case example can be reassigned again.
let example
throw new Error
example = 5
This happens when you introduce the temporal dead zone to the global scope. As you might know, let declarations are hoisted but left uninitialised. Due to control flow, it can happen that a variable is never initialised:
function …() {
if (false)
example; // would throw a ReferenceError if it was evaluated
… // do something
if (true)
return; // stop!
let example = 5; // never executed
}
This is fine in a function scope. Maybe something went wrong, maybe the variable wasn't needed at all - in the next call, a new scope with a new variable will be created.
A similar thing can happen in the global scope, when you throw an exception before the variable is initialised (only exceptions work here as a control flow construct, nothing else achieves the same effect).
throw new Error;
let example = 5;
In contrast to the function scope, it does matter here that the variable stays uninitialised. The global scope lasts forever, and the variable is eternally dead. It was not and will never be initialised, and lexical variables cannot be re-declared (which helps preventing mistakes).
This was discussed on es-discuss, but deemed irrelevant. If top-level <script> execution throws an error, you have bigger problems than uninitialised variables. There is no path to recover. If you need one (e.g. by trying to re-declare it in successive scripts), you have to use var anyway.
That you have the same problem in the devtools console is a bit of a nuisance, but could be solved for the console as a special scope.
You should know about hoisting in JS. Basically, a declaration like this
let a = werwr;
is interpreted as
let a;
a = werwr;
And that why a is already declared when you run your second line of code.
UPDATE
So, there is a NOTE in ES specs
https://tc39.github.io/ecma262/#prod-LetOrConst
let and const declarations define variables that are scoped to the
running execution context's LexicalEnvironment. The variables are
created when their containing Lexical Environment is instantiated but
may not be accessed in any way until the variable's LexicalBinding is
evaluated. A variable defined by a LexicalBinding with an Initializer
is assigned the value of its Initializer's AssignmentExpression when
the LexicalBinding is evaluated, not when the variable is created. If
a LexicalBinding in a let declaration does not have an Initializer
the variable is assigned the value undefined when the LexicalBinding
is evaluated.
...
"but may not be accessed in any way until the variable's LexicalBinding is evaluated"
means that, the declaration must be successfully completed before you can access the variable (either getting value, assigning value, or doing typeof, or event delete);
In your case, the variable's LexicalBinding is interupted by the exception.
let a = werwr // Uncaught ReferenceError: werwr is not defined
Please follow the link and read more about that. If you find a way to recover varaible a, please tell me.
Thanks, today I found out something new about Javascript.

Javascript hoisting and variable assignment (with no declaration)

Looking at MDN's introduction to JavaScript, Grammar and Types section - one reads:
Declaring variables
You can declare a variable in three ways:
With the keyword var. For example, var x = 42. This syntax can be used to declare both local and global variables.
By simply assigning it a value. For example, x = 42. This always declares a global variable. It generates a strict JavaScript
warning. You shouldn't use this variant.
With the keyword let. For example, let y = 13. This syntax can be used to declare a block scope local variable. See Variable scope
below.
The following code snippet would seem to fit the "by simply assigning it a value" scenario, meaning the variable should be treated as global.
(function(){
console.log(myVar);
//the following will throw a ReferenceException error
//myVar = 10;
//the following will not, and I can understand it following the defintion of the behavior of using `var` keyword
//var myVar = 10;
})();
But running the code will generate a ReferenceException when myVar is commented, and undefined when not. I would expect it to generate undefined in both cases, since if myVar is a global variable (per definition), than javascript's variable hoisting would make it known before reaching console.log(myVar);
What is the explanation behind such behavior ? (the behavior I described is what I get when trying it in my firefox's console, but running it in jsfiddle will not throw an error).
Are self-executing functions an exception to hoisting ?
the "by simply assigning it a value" scenario
You are reading the value, not assigning it
if myVar is a global variable (per definition),
It isn't.
myVar is:
a variable scoped to the function if the function contains var myVar (or function myVar () { ... }, or it is listed as a parameter in the function definition).
a variable scoped to the block if the block contains let myVar
a global variable if a value has been assigned to it previously and neither of the above conditions are true.
Since you haven't assigned a value, it isn't a global. Since none of the above conditions are true, it isn't any kind of variable, so you get a reference error.
Regarding your comment:
I left my var when I meant var in the scenario I am trying to depict. Updated question.
… and the edit to which you refer:
Commented out code is not evaluated. Having a comment that uses the keyword var doesn't do anything.
Regarding your further edits.
You get a reference error if you try to read a variable before it has been declared.
var statements (and function declarations) are hoisted so a variable declared with those methods can be read anywhere in the function.
Assignments are not hoisted. A global variable created implicitly by assignment (which is generally not considered to be best practise and is banned in strict mode) can't be read before the value is assigned.
Since my comment seemed to help explain it to you, I will turn it into an answer:
Implicit global variable creation (when you don't actually declare it, but just assign to it) is NOT hoisted. The variable creation happens inline at the moment the assignment occurs.
Thus, when you try to read the variable, it does not exist yet and that is an error.
var or let declarations are hoisted to the top of their appropriate scope.
All of this should hopefully help explain why you should just run in strict mode where implicit global creation is illegal and not allowed and triggers an immediate error. It's basically evil. A simple typo misspelling a variable may not trigger an error when you really want it to.

Do let statements create properties on the global object?

In JavaScript, var declarations create properties on the global object:
var x = 15;
console.log(window.x); // logs 15 in browser
console.log(global.x); // logs 15 in Node.js
ES6 introduces lexical scoping with let declarations that have block scope.
let x = 15;
{
let x = 14;
}
console.log(x); // logs 15;
However, do these declarations create properties on the global object?
let x = 15;
// what is this supposed to log in the browser according to ES6?
console.log(window.x); // 15 in Firefox
console.log(global.x); // undefined in Node.js with flag
Do let statements create properties on the global object?
According to the spec, no:
A global environment record is logically a single record but it is specified as a composite encapsulating an object environment record and a declarative environment record. The object environment record has as its base object the global object of the associated Realm. This global object is the value returned by the global environment record’s GetThisBinding concrete method. The object environment record component of a global environment record contains the bindings for all built-in globals (clause 18) and all bindings introduced by a FunctionDeclaration, GeneratorDeclaration, or VariableStatement contained in global code. The bindings for all other ECMAScript declarations in global code are contained in the declarative environment record component of the global environment record.
Some more explanation:
A declarative environment record stores the bindings in an internal data structure. It's impossible to get a hold of that data structure in any way (think about function scope).
An object environment record uses an actual JS object as data structure. Every property of the object becomes a binding and vice versa. The global environment has an object environment object whose "binding object" is the global object. Another example is with.
Now, as the cited part states, only FunctionDeclarations, GeneratorDeclarations, and VariableStatements create bindings in the global environment's object environment record. I.e. only this bindings become properties of the global object.
All other declarations (e.g. const and let) are stored in the global environment's declarative environment record, which is not based on the global object.
Standard scripts:
Both let and var variables, if declared at the top-level of a script, are accessible outside of the script file. However, only var variables get assigned to the window object. Have a look at this code snippet as proof:
<script>
var namedWithVar = "with var";
let namedWithLet = "with let";
</script>
<script>
console.log("Accessed directly:");
console.log(namedWithVar); // prints: with var
console.log(namedWithLet); // prints: with let
console.log("");
console.log("Accessed through window:");
console.log(window.namedWithVar); // prints: with var
console.log(window.namedWithLet); // prints: undefined
</script>
Javascipt modules:
Note that modules are a different story. Variables declared in a module are not made available in the global scope:
<script type="module">
var namedWithVar = "with var";
let namedWithLet = "with let";
</script>
<script>
console.log(namedWithVar); // ReferenceError
</script>
<script>
console.log(namedWithLet); // ReferenceError
</script>
<script>
console.log(window.namedWithVar); // prints: undefined
console.log(window.namedWithLet); // prints: undefined
</script>
Per the specification:
"let and const declarations define variables that are scoped to the running execution context’s LexicalEnvironment."
This means that you should be able to access the variable inside the execution scope, but not outside. This expands the execution scope beyond the classic JS closure structure of function-only or global.
Defining a let variable globally should not expose the variable on the global context, as used to be the case in Firefox. In practice you should not define variables in a global context.
Variables declared via let keyword do not create accessible properties on a global object (window for a browser).
Actually, Firefox fixed its behavior: let v = 42; 'v' in window // false
let allows you to declare variables that are limited in scope to the block, statement, or expression on which it is used. This is unlike the var keyword, which defines a variable globally, or locally to an entire function regardless of block scope.
At the top level of programs and functions, let, unlike var, does not create a property on the global object. For example:
var x = 'global';
let y = 'global';
console.log(this.x); // "global"
console.log(this.y); // undefined
The scope of a variable declared with var is its current execution context, which is either the enclosing function or, for variables declared outside any function, global. If you re-declare a JavaScript variable, it will not lose its value. For example:
var x = 1;
if (x === 1) {
var x = 2;
console.log(x);
// output: 2
}
console.log(x);
// output: 2
Note: that unlike C, C++, and Java, JavaScript does not have block-level scope when you declare a variable using var.
As we mentioned before let allows you to declare variables that are limited in scope to the block, statement, or expression on which it is used. For example:
let x = 1;
if (x === 1) {
let x = 2;
console.log(x);
// output: 2
}
console.log(x);
// output: 1
Here I recommend you to read about Variable Scope

Categories