How can an identifier be both already declared and not defined? - javascript

I was messing around in the JS console and I stumbled across some perplexing behavior. The second error (SyntaxError) makes sense to me, I already declared (or tried to at least) bar so I shouldn't be able to declare it again. However, I would expect bar to be undefined in this case.
How can a variable be both declared and not defined? Can anyone explain what is going on internally?
let bar = fo.map(i => console.log(i)) //typo
VM2927:1 Uncaught ReferenceError: fo is not defined
at <anonymous>:1:11
(anonymous) # VM2927:1
let bar = foo.map(i => console.log(i)) //fix typo
VM2999:1 Uncaught SyntaxError: Identifier 'bar' has already been declared
at <anonymous>:1:1
(anonymous) # VM2999:1
bar
VM3019:1 Uncaught ReferenceError: bar is not defined
at <anonymous>:1:1
.as-console-wrapper {max-height: 100% !important;}
<script>
//typo:
let bar = fo.map(i => console.log(i)) //Uncaught ReferenceError: fo is not defined
</script>
<script>
//fix typo:
let bar = foo.map(i => console.log(i)) //Uncaught SyntaxError: Identifier 'bar' has already been declared
</script>
<script>
console.log(bar) //Uncaught ReferenceError: bar is not defined
</script>

Actually, it can't at all.
TL;DR: In your code, the variable bar is declared and defined, but not initialized.
The first error is right: bar was declared twice.
Also note, that it's a compile-time SyntaxError, so it happens before the code gets evaluated, so it isn't affected by the thrown exception inside the variable declaration:
//SyntaxError
console.log('Evaluating code') //Never runs
let foo = 'bar'
let foo = 'baz'
But the second error isn't so obvious: why isn't bar just undefined?
After a lot of searching in the ECMAScript 6 specification, I've found the source of the problem. That's a "bug" (or at least a situation that hasn't taken care of) in the spec itself, but fortunately, it's very rare outside of a JS console.
You may know, that let and const variables have a so-called temporal dead zone, that throws ReferenceErrors when you try to look up or assign to variables before they're declared:
/* Just to make console fill the available space */
.as-console-wrapper{max-height:100% !important;}
<!-- Using separate scripts to show all errors -->
<script>
console.log(foo) //ReferenceError
const foo = 'bar'
</script>
<script>
bar = 'baz' //ReferenceError
let bar
</script>
That's because these variables' bindings are created before the containing code block get executed, but aren't initialized until the variable declaration statement evaluated. Trying to retrieve or modify the value of an uninitialized binding always results in a ReferenceError.
Now, let's inspect the evaluation of the let (and const) statement's LexicalBinding (variable = value pair), it's defined as follows:
LexicalBinding : BindingIdentifier Initializer
Let bindingId be StringValue of BindingIdentifier.
Let lhs be ResolveBinding(bindingId).
Let rhs be the result of evaluating Initializer.
Let value be GetValue(rhs).
ReturnIfAbrupt(value).
If IsAnonymousFunctionDefinition(Initializer) is true, then
Let hasNameProperty be HasOwnProperty(value, "name").
ReturnIfAbrupt(hasNameProperty).
If hasNameProperty is false, perform SetFunctionName(value, bindingId).
Return InitializeReferencedBinding(lhs, value).
Emphasis mine.
Without really going into detail, I'd like to highlight the most important things:
BindingIdentifier is the variable name
Initializer is the value to assign to it
ReturnIfAbrupt() is an abstract algorithm, that returns from its caller with its argument if its argument is a Completion Record that represents an abrupt completion (e.g. a thrown exception)
InitializeReferencedBinding() initializes the given Binding
The problem appears when an exception is thrown during the evaluation of the Initializer.
When that happens, this:
GetValue(rhs)
...will return an abrupt completion, so the following line:
ReturnIfAbrupt(value)
...returns from the let (or const) statement with the abrupt completion record (i.e. re-throws the exception), so that line:
InitializeReferencedBinding(lhs, value)
...won't run at all, therefore, the variable's binding remains uninitialized and continues to throw ReferenceErrors when you try to look it up or assign to it.
These errors' message (foo is not defined) is even more confusing and inappropriate, but that depends on the implementation, so I can't reason about it; however, probably it's because of another unhandled case.

let foo = undefined; this is a declared undefined variable, if you use it somewhere you will get foo is undefined if you try to declare it again you will get an error SyntaxError: redeclaration, some functions return undefined when they fail, the variable you use to store the return value will be declared and undefined in the same time. in this example you can use foo but you can't re-declare it for example let foo = undefined; foo = 5;

Related

Const is not defined in global scope but defined in local?

Why const is not defined in global scope but defined in local?
screenshoot from devTools
{
console.log(b);
const b = 2;
}
VM366:2 Uncaught ReferenceError: Cannot access 'b' before initialization
at <anonymous>:2:17
console.log(a);
const a = 1;
VM382:1 Uncaught ReferenceError: a is not defined
at <anonymous>:1:13
This seems to be related to some "magic" in how REPL input is handled. It's not really a property of how JavaScript normally works.
If you try this in a node.js file, in a <script>, or even in an (0, eval)('...'), you'll get "Cannot access 'a' before initialization" as expected. Note that even in the DevTools console this won't happen if you are in a paused debugger state.
In a non-paused state (only there), the DevTools allow you to declare variables as if you were continuously writing a script, and the declarations will persist across commands. There is clearly "magic" outside of regular JavaScript involved, because even eval would create another temporary scope. That "magic" is handled by V8 itself. It is therefore pretty likely that whatever trickery is used here has the side effect of this slightly unexpected error. (I initially thought the side effect is that the variable declaration isn't hoisted, but if you do a = 0; const a = 1 then you get "Assignment to constant variable", so that doesn't seem to be the case.)
I tried to trace this to the source, but I got lost in the depths of V8. Nonetheless, I discovered (by opening another DevTools to debug the DevTools themselves!) that this only happens if the replMode argument (that gets passed to the V8 backend's evaluate method) is true. This can be verified by setting a breakpoint here and changing the value of options.replMode.
replMode is documented as follows:
Setting this flag to true enables let re-declaration and top-level await.
Note that let variables can only be re-declared if they originate from replMode themselves.
Hoisting workd differently for const and let than var.
SO for const and let, which are block scoped gives ReferenceError error as they do not initialize value as undefined.
-Whereas, for var it will initialize value as undefinded if you do not assign any v alue and var is function scoped. -You can see console from devToolis giving different error, whereasconsolefromstackoverflow``` giving error as our expectation that
"Uncaught ReferenceError: Cannot access 'a' before initialization.
So I believe it's how devtool console treating the error for block-scope and global-scope.
-Check it on ```morzila-devtool`.
{
console.log("before initialization:" + b);
var b = 2;
console.log("after initialization:" + b);
}
console.log("before initialization:" + a);
const a = 1;
This is from Firefox browser
This is from Microsoft Edge
I came to know from this post that, Chrome and IE both have a global event object. So when we are trying to access it first hoisting won't work for const and let. so it's says that we cannot access but window object has access to those variables, So I assume it's throwing error for a is not defined for window object, which is global object.
window object sets automatically to undefind. const window.variable = undefined (when not defined);
Try to run this code:
{console.log(b); var b=1; console.log(window.c); const c=1;}
OUTPUT:
1 debugger eval code:1:9
undefined // window.c = undefined
And as window object has access to global scope it gives error for a or b or variable is not defined when we access it in global scope before initialisation using let or const.

Weird (let) variable assignment behavior in node repl - can't assign values

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.

Why does re-declaring an argument inside of a try/catch throw a ReferenceError?

I mistakenly wrote a re-declaration of an argument as a const in a function and instead of throwing SyntaxError: Identifier 'bar' has already been declared I ended up with ReferenceError: bar is not defined..
What causes this behaviour? It wasn't the expected error, and left me confused for a few minutes.
Example code:
function foo(bar) {
try {
console.log(bar);
const bar = 123;
} catch(err) { console.log(err) }
}
foo(456);
If I don't wrap the declaration in a try/catch, I get (what I believe to be) the expected error.
Constants are block-scoped, much like variables defined using the let statement.
From this MDN article.
Since you wrapped bar inside a block of braces, its definition is relative to that block. And because you have another bar declaration inside of that block, despite being after the call to it, the compiler will attempt to use this newly defined bar instead of the passed-in parameter. Rename them as separate parameters to mitigate confusion, since one can assume they are holding different data because of your declaration.
It's a myth that const and let aren't hoisted at all. They're half-hoisted. :-) That is: Within a block, if const bar or let bar (or class bar { } for that matter) appears anywhere in that block, then bar cannot be used prior to that declaration in the block — even if it exists in a containing scope. This area between the beginning of the block and the declaration is called the Temporal Dead Zone:
function foo(bar) {
// `bar` is fine here
try {
// Temporal Dead Zone, `bar` cannot be used here
console.log(bar);
// End of TDZ
const bar = 123;
// `bar` would be fine here
} catch(err) { console.log(err) }
}
foo(456);
Because const declarations are block scoped, the const bar declaration gets hoisted to the start of the block. (Just after try {)
That means that bar isn't actually defined when you try to log it, because the hoisting overrides / hides the parameter bar.
Taken from here https://tylermcginnis.com/videos/var-let-const/
var is function scoped and if you try to use a variable declared with
var before the actual declaration, you'll just get undefined. const
and let are blocked scoped and if you try to use a const or let
variable before the declaration you'll get a reference error.

What is the logic behind when JavaScript throws a ReferenceError?

I've been using JavaScript for years but have been trying to increase my deep, under-the-hood type knowledge of the language lately. I'm a bit confused about what the logic is behind when JavaScript throws a ReferenceError.
For example, none of these throw a referenceError, but still write undefined to the console:
function foobar(foo)
{
var bar = foo;
console.log(bar);
}
foobar();
or
var foo = undefined;
var bar = foo;
console.log(bar);
or
var foo;
var bar = foo;
console.log(bar);
but this obviously does throw a ReferenceError error on the first line without writing to the console:
var bar = foo;
console.log(bar);
So it seems that having a variable in a parameter list or declaring it will stop a referenceError from being thrown - even though the variable is still 'undefined'.
Does anyone know what's going on under the hood or what the hard and fast rules are surrounding this? Does anyone know why these aren't considered referenceErrors?
There's a difference in using a variable that exists but has an undefined value, and using a variable that doesn't exist and was never declared.
The latter will create a reference error as you're trying to reference something that doesn't exists and has not been declared.
On the other hand, when you do
var foo;
foo does exists, and it has been declared, it's value is just undefined, so you can still reference it without throwing an error.
In other words, trying to reference a variable that hasn't been declared will throw a reference error, while referencing declared variables will never throw a reference error, regardless of wether or not a value has been set for that variable.

ReferenceError and the global object

In JavaScript in the browser window is the global object, which means every variable defined in the global scope is a child of window. So why do I get this result:
console.log(window.foo); // No error, logs "undefined".
console.log(foo); // Uncaught ReferenceError: foo is not defined.
Fiddle
Those two lines should be the same, shouldn't they?
Because with window.foo you are explicitly looking for foo property of window object which is not the case in latter option. In the latter option, if foo isn't defined, you should as developer be able to know that it isn't defined and get the clear error warning rather than interpreter setting it to undefined on its own (like first case) which will lead to unexpected results.
Reference Error:
Represents an error when a non-existent variable is referenced.
A ReferenceError is thrown when trying to dereference a variable that has not been declared.
Take a look at this article for more info:
Understanding JavaScript’s ‘undefined’
Quoting from above article:
A Reference is considered unresolvable if its base value is undefined. Therefore a property reference is unresolvable if the value before the dot is undefined. The following example would throw a ReferenceError but it doesn’t because TypeError gets there first. This is because the base value of a property is subject to CheckObjectCoercible (ECMA 5 9.10 via 11.2.1) which throws a TypeError when trying to convert Undefined type to an Object.
Examples:
var foo;
foo.bar; //TypeError (base value, foo, is undefined)
bar.baz; //ReferenceError (bar is unersolvable)
undefined.foo; //TypeError (base value is undefined)
References which are neither properties or variables are by definition unresolvable and will throw a ReferenceError, So:
foo; //ReferenceError
In your first example (window.foo) you are accessing a property of the window object. JavaScript returns "undefined" for when you are trying to access a non existent property of a object. It's designed that way.
In the second example you are referencing a variable directly, and since it does not exists an error is raised.
It's just the way JavaScript is designed and works.
In JavaScript you can assign object fields on the fly like that, so window.foo is nearly (see comments below) equivalent to var foo; when defined in the global context, whereas just calling foo out of the blue makes the browser panic 'cause it down't even know which object to look in. Notice, if you do:
//when in global context, 'var' sets a property on the window object
var foo;
console.log(foo);
//it will then also log `undefined` instead of throwing the error.
//if you then do:
foo = "abbazabba";
console.log(window.foo);
// it will return "abbazabba"

Categories

Resources