if(true) {
tmp = 'abc';
console.log(tmp);//which should throw referenceError but not
let tmp;
console.log(tmp);
tmp = 123;
console.log(tmp);
}
This code results in
abc
undefined
123
Why does the first console.log(tmp) not throw an error?
why it should throw a referenceError
In ECMAScript 2015, let will hoist the variable to the top of the block. However, referencing the variable in the block before the variable declaration results in a ReferenceError. The variable is in a "temporal dead zone" from the start of the block until the declaration is processed.
the problem is bable settings,i think.
so,maybe it is a bug of babel?
https://github.com/babel/babel.github.io/issues/826
You are correct, in ES6 this does throw an exception. There's two reasons why it doesn't for you:
node.js already implemented let - but it works correctly only in strict mode. You should use it.
babel does not appear to transpile the TDZ by default, as it is quite complicated and leads to lengthy code. You can however enable it with the es6.blockScopingTDZ/es6.spec.blockScoping option (but I'm not sure whether this worked in Babel 5 only and what happened in Babel 6 to them).
Statement
tmp = 'abc';
is not elegant but still OK in normal mode (except let keyword which is not allowed outside strict mode). It will simply create global variable. However, the code is not correct and will throw an error only when you executed this code in "strict mode". In this mode you have to declare all variables with one of this keywords:
var
let
const
'use strict'
if(true) {
tmp = 'abc';
console.log(tmp);//which should throw referenceError and now it does
let tmp;
console.log(tmp);
tmp = 123;
console.log(tmp);
}
No, it shouldn't throw a reference error.
The variable is implicitly declared (in the global scope) when you assign to it.
Then, later, you declare a new variable with the same name but a tighter scope. The new variable is not hoisted because it is declared using let.
I can't give a more precise answer, because you did not explain why you think you should get a reference error.
Related
I'm using Node v12.14.1 on Windows 10 in cmd.
When assigning the value of an undefined function to a variable:
let a = f();
I get:
Thrown:
ReferenceError: f is not defined
Which is fine. But when I try:
a = 2;
I now get:
Thrown:
ReferenceError: a is not defined
And when I try:
let a = 2;
I get:
Thrown:
SyntaxError: Identifier 'a' has already been declared
So, a variable declared using let, when assigned the value of an undefined function, has its identifier already declared, and is not defined, at the same time.
Is this intended? Am I missing something here? Is this a bug?
The same does not happen when using var in the undefined function assignment, or when not using anything (global variable).
REPLs are funny, but no, this behavior isn't a bug, it is indeed per spec. It's something you couldn't see in a non-REPL environment, though.
A let statement creates a binding¹ upon entry to the scope where let appears, but doesn't initialize it (unlike var, which initializes it with undefined). (In the case of the REPL "entering the scope" is basically just before it executes the code you've given it.) The initialization happens when the initialization part of the let statement happens, later when you you reach that statement in the step-by-step execution.² In your code, though, you never reach that part of the statement, because when the initializer was evaluated, it threw an error.
At that point, there's nothing you can do with a, because it exists but isn't initialized, and the only thing that could have initialized it (the result of the initializer in the original let a = f();) failed and can't be run again.
The reason you can't see that in non-REPL code is that the error would take you out of the scope where a has been created but not initialized. consider:
try {
let a = f(); // ReferenceError
// Execution in this block never continues
} catch {
// Execution arrives here...but `a` is not in scope
}
¹ binding - an entry for the variable in the execution context's environment record
² If the let statement doesn't have an initializer, undefined is used at this point.
I've been trying to figure out how the temporal dead zone/parsing of let and const work. This is what it seemingly boils down to (based on documentation and various responses I received in previous questions [such as this and this], though this goes against some answers given disagreement). Is this summary correct?
At the top of the scope, the JS engine creates a binding (an association of the variable keyword and name, e.g., let foo;) at the top of the relevant scope, which is considered hoisting the variable, but if you try to access the variable before the location of its declaration, JS throws a ReferenceError.
Once the JS engine moves down to the declaration (synonymous with "definition"), e.g., let foo;, the engine initializes it (allocating memory for it and making it accessible). The declaration is self-binding. (Here's the part that doesn't make sense to me: the binding is causing the hoisting at the top, but the engine doesn't initialize until it reaches the declaration, which also has a binding effect.) If there isn't an assignment, the value of the variable is set to undefined in the case of let or, if const is used, a SyntaxError will be thrown.
For reference here's what the specs say about it:
ECMAScript 2019 Language Specification draft: section 13.3.1, Let and Const Declarations
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.
MDN Web Docs: Let
let bindings are created at the top of the (block) scope containing
the declaration, commonly referred to as "hoisting". Unlike variables
declared with var, which will start with the value undefined, let
variables are not initialized until their definition is evaluated.
Accessing the variable before the initialization results in a
ReferenceError. The variable is in a "temporal dead zone" from the
start of the block until the initialization is processed.
Maybe first you need to understand why the TDZ exists: because it prevents common surprising behaviour of variable hoisting and fixes a potential source of bugs. E.g.:
var foo = 'bar';
(function () {
console.log(foo);
var foo = 'baz';
})();
This is a frequent cause of surprise for many (novice) programmers. It's too late to change the behaviour of var now, so the ECMAScript group decided to at least fix the behaviour together with the introduction of let and const. How exactly it is implemented under the hood is a somewhat moot point, the important thing is that it stops what it most likely a typo/structural mistake dead in its tracks:
let foo = 'bar';
(function () {
console.log(foo);
let foo = 'baz';
})();
Practically speaking Javascript is executed in a two-step process:
Parsing of the code into an AST/executable byte code.
Runtime execution.
The parser will see var/let/const declarations in this first step and will set up scopes with reserved symbol names and such. That is hoisting. In the second step the code will act in that set up scope. It should be somewhat obvious that the parser/engine is free to do whatever it wants in that first step, and one of the things it does is to flag the TDZ internally, which will raise an error at runtime.
TDZ is quite complex to understand and requires a blog post to clarify how that actually works.
But in essence, overly simplified explanation is
let/const declarations do hoist, but they throw errors when accessed before being initialized (instead of returning undefined as var would)
Let's take this example
let x = 'outer scope';
(function() {
console.log(x);
let x = 'inner scope';
}());
the code above will throw a ReferenceError due to the TDZ semantics.
All these are from this great article completely about TDZ.
Kudos for the author.
I am running Edge/15.15063. 'Can I Use' says const should work.
Running:
const x = 'woo'
Then:
console.log(x)
Returns
'x' is undefined
Screenshot:
Why isn't const working?
I suspect that the Edge console is using a with statement under its covers like other implementations did. This would explain vars and even function declarations being hoisted outside into the global scope, but let and const will be locked into the block scope:
with (…) {
const x = 'woo'
}
// next input:
with (…) {
console.log(x) // obviously undeclared
}
Try entering them in multiline mode, in a single evaluation - there they should work.
But you also might want to file a bug, as the console is indeed expected to feel like evaluating things in the global scope.
I think I figured this out, but this is as much a guess as an answer. Too long for a comment though.
I think what's happening is that const and let do not create implicit globals when used in the top-level scope in the same way var does. Although top-level variables created with const and let are global, they are not properties of the global window object.
If the MS console is relying on that implicit window property creation for accessing variables created in the console, then const and let will not work.
I am unsure of the inner workings of Chrome Dev Tools, but it seems to create an anonymous function wrapper for code executed in the console:
throw new Error;
VM679:1 Uncaught Error
at anonymous:1:7
(function() { throw new Error; })();
VM759:1 Uncaught Error
at anonymous:1:21
at anonymous:1:33
I am unsure if there is other sandboxing going on here, I didn't necessarily find a lot of documentation on it.
ES5 typeof is considered safe, as it will not throw ReferenceError when checked agains a non-declared value. such as
console.log(typeof undeclaredVar); // undefined
however, when checking for typeof undeclaredLetConst in es6 it will throw an error only if the value was later on declared with a let or const. if it was declared with var it will work normally.
console.log(typeof undeclaredLetConst);
let undeclaredLetConst = "hello"; // ReferenceError
whats happening there?
Why it works with var declarations
When a JavaScript engine looks through a lexical scope block and finds a variable declaration with var, it hoists the declaration to the top of the function (or global scope if no "use strict" is present).
Hence the typeof will never fail as the variable its checking upon will be hoisted beforehand.
The Temporal Dead Zone (TDZ)
The TDZ is never named explicitly in the ECMAScript specification, but the term is used to describe why let and const declarations are not accessible before their declaration.
Why it fails with const and let
When a JavaScript engine looks through a lexical scope block and finds a variable declaration with let or const, it places the declaration in the TDZ. Any attempt to access a variable in the TDZ results in a runtime error.
The declaration is removed from the TDZ during runtime once the flow reaches the declaration itself.
console.log(typeof undeclaredLetConst); // "undefined"
if (1) {
let undeclaredLetConst = "no errors!";
}
undeclaredLetConst isn’t in the TDZ when typeof operation executes because it occurs outside of the block in which undeclaredLetConst is declared. That means there is no value binding, and typeof simply returns "undefined".
Source: An awesome book by Nicholas C. Zakas, Understanding ECMAScript 6.
My Problem Lies here I'm learning JavaScript But not new to Programming at all.
I understand hoisting, but with strict mode shouldn't this produce an error and be caught either when 6 is assigned to undeclared a variable or document.getElement... is assigned x this doesn't produce an error so my diagnosis is that hoisting is still going on..which i don't like and want to get rid of with using strict. Using Chrome Version 42.0.2311.152 m as my browser
function strictMode(){
'use strict';
try {
x = 6;
document.getElementById('hoisting').innerHTML = x;
var x;
}
catch(err) {
document.getElementById('error_report').innerHTML =
"There was an error that occured (Were in Strict Mode)" +
" " + err.message;
}
}
Variable declarations (i.e. var x;) are valid for the entire scope they are written in, even if you declare after you assign. This is what is meant by "hoisting": the var x; is hoisted to the beginning of the scope, and the assignment x = 6; is fine because x has been declared somewhere in that scope.
Strict mode does not change any of this. It would throw an error if you omitted the var x; declaration altogether; without strict mode, the variable's scope would implicitly be the global scope.
In ES2015 (a.k.a. ES6), hoisting is avoided by using the let keyword instead of var. (The other difference is that variables declared with let are local to the surrounding block, not the entire function.)
There are some weird things javascript allows that, as someone learning the language, you must learn to combat with good coding practices (simicolons are another good example). In the case of hoisting, it is generally good practice to declare your variables at the top of the scope where they would be hoisted to anyway. As already mentioned, strict mode is not a silver bullet and will not enforce this for you.