What is the logic behind when JavaScript throws a ReferenceError? - javascript

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.

Related

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

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;

Why erroneous "let/const" declarations create inaccessible variables in a REPL? [duplicate]

This question already has answers here:
Weird (let) variable assignment behavior in node repl - can't assign values
(1 answer)
Chrome console already declared variables throw undefined reference errors for let
(2 answers)
Closed 3 years ago.
While coding in a Node.js REPL, I accidentally declared a new symbol by using the new keyword and since Symbol() is not a constructor, I got an error as shown in this snippet,
let sym = new Symbol("some description");
// TypeError: Symbol is not a constructor
// at new Symbol (<anonymous>)
So I thought that's fine, I'll do it the right way but I got another error.
let sym = Symbol("some description");
// SyntaxError: Identifier 'sym' has already been declared
That was strange to me because I expected that since my declaration caused an error, the variable has not been defined at all but now I see it seemingly is defined. My next guess was that maybe it got an undefined value, so I tried to reach to this variable and see what value it owns.
console.log(sym);
// ReferenceError: sym is not defined
WHAT? So the variable sym is both defined and undefined?
I even tried to use the block scope of an object so that I can check whether the variable does actually exist in the object using the Object.prototype.hasOwnProperty() function but unfortunately, the same problem emerged and the whole object became undefined and inaccessible.
let obj = {
a: 4,
b: new Symbol("some description"),
};
// TypeError
console.log(obj);
// ReferenceError
let obj = {};
// Syntax Error
I found that this issue could go away if I use var declarations, so I assume it has something to do with block scopes that I don't know.
So I have 3 questions with respect to this problem:
Where is the variable sym defined and where is it undefined? Any connection between this case and the idea of "Temporal Dead Zone"?
What value does sym own?
How can I assign a new value to the sym variable?
Is this problem specific to this case or any erroneous let/const declaration will result in the same issue?

JavaScript variable hoisting behavior [duplicate]

This question already has answers here:
Are variables declared with let or const hoisted?
(7 answers)
Closed 4 years ago.
I'm probably missing something very fundamental but I would like to ask this regardless
var a=100;
function f(){
console.log(a)
const a=150
}
console.log(a)
f();
Prints 100 and throws an error while changing const a=150 to var a=150 returns 100 and undefined. I'm unsure why this behavior occurs and any pointers to relevant info is appreciated
In general, this is how hoisting works:
the declaration of the variable is moved to the top
the variable is initialized with a special "hoisted" value
when the program reaches the var/let/const line, the variable is re-initialized with the value mentioned on that line (or undefined if there's none).
Now, your example can be simplified down to this:
console.log(a)
let a = 150
which is actually:
a = <hoisted value>
console.log(a)
a = 150
It throws an error because, for let and const, the hoisted value is a special object that raises an error when you try to access it.
On the other side, the hoisted value for var is just undefined, so this will print undefined without throwing an error:
console.log(a)
var a = 150
Also, there's some confusion (including this very thread) about which variable types are hoisted, and a so-called "dead zone" for let/const vars. It's simpler to think of things this way: everything is hoisted, that is, all variable bindings in a block are created before entering the block. The only difference between var and let/const in this regard is that with the latter you are not allowed to use a binding until you initialize it with a value.
See https://stackoverflow.com/a/31222689/989121 for more details.
const and let have temporal dead zone so when you try to access const or let before it is instantiated you get an error.
This is not true for var. var is hoisted at the top meaning it is instantiated with a value of undefined just before a function code is run.
The problem is that with new ES6, let and const, hoisting is not applied what this means is that a variable declared with either let or const cannot be accessed until after the declaration, if you do this will throw and error
var a=100;
function f(){
console.log(a)// throws an error because is accessed before is declared....
const a=150
}
console.log(a)
f();
This is commonly called the Temporal Dead Zone
On the other hand using Var will apply hoisting what this does is defines the variable in memory before all the scope is executed...
var a=100;
function f(){
console.log(a)// This won't throw an error
var a=150
}
console.log(a)
f();
the problem is
the variable with const cannot be declared more then once,
and when you declares const a=150 inside a function, it deleted the previous variable and this is why the error is coming
I think the answer your looking for is const is block scope and is not hoisted, whereas var is hoisted.
That's why you get 100, "Error: a is not defined" with:
var a=100;
function f(){
console.log(a)
const a=150
}
console.log(a)
f();
and 100, undefined with:
var a=100;
function f(){
console.log(a)
var a=150
}
console.log(a)
f();
In the second instance a is declared, but it is not defined yet. You'll see the same result with:
function f() {
console.log(a)
var a=150
}
console.log(a)
f();
Since the var inside the function is hoisted.
Bottom line: const variables are not hoisted, var variables are.

Use of var in the global namespace

I'm reading "you don't know javascript" and I find some trouble with one example in the book "This & Object Prototypes".
When discussing the different rules for this, specifically in the "Implicit binding" paragraph, the author gives this example:
function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
};
var bar = obj.foo; // function reference/alias!
var a = "oops, global"; // `a` also property on global object
bar(); // "oops, global"
However when trying this on JSFiddle I get an undefined output in console instead of the "oops, global".
Conversely, if I define a without var or using window.a I get the output intended by the author regardless of strict mode.
Why is this happening? Did something in ES6 change the way global variables should be declared?
The default settings for JS Fiddle wrap the JS in a function and assign it as an load event handler.
Your tests are not in the global scope.

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