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

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.

Related

How is it possible an object has a property but access to it results in ReferenceError?

// a.js
import * as bModule from './b.js'
export const b = bModule
// b.js
import * as aModule from './a.js'
export const a = aModule
If a.js is an entry point of our app then b.js would run firstly. Calling of console.log(aModule) from b.js at this stage results in such output:
[Module] {
b: <uninitialized>,
}
And if b.js try to access aModule.b it will end up with the ReferenceError: b is not defined.
ReferenceError is thrown when a code uses an identifier (variable) hasn't declared yet. However, in this case aModule does have the property b (the fact is borne out by console.log). Moreover, when we access an uninitialized object property we just get the undefined value but not the exception.
So how this behavior can be understood? Is it specified?
This behavior is because of the temporal dead zone.
Unlike identifiers declared using var, identifiers declared using let or const are marked as "not yet initialized" and they can't be accessed until their declaration has actually executed during step-by-step execution of the javascript code.
Since b in a.js is defined with const, it is hoisted but you can't access it before its declaration has actually executed. Same would be the case if b was declared with let.
If you declare b using var, then you would see undefined as the output.
ReferenceError is thrown when a code uses an identifier (variable) hasn't declared yet.
Not only then. :-)
Moreover, when we access an uninitialized object property we just get the undefined value but not the exception.
Yes, but modules are modern constructs, and you're using those another modern construct: let. It's defined to fail if used before it could be initialized, rather than provide ambiguous values.
Yes, it's specified behavior. Modules go through a series of phases, and during that process their exports of let, const, or class bindings are created as uninitialized bindings, and then later those exports are initialized with values. When modules have cycles (circular dependencies), it's possible to see an export before it's initialized, causing a ReferenceError.
It's the same Temporal Dead Zone (TDZ) we can see here:
let a = 21;
console.log(a); // 21
if (true) {
console.log(a); // ReferenceError
let a = 42;
}
The declaration for a within the if block declares it throughout the block, but you can't access it until the declaration is reached in the step-by-step execution of the code. You can see that the declaration takes effect throughout the block by the fact that we can't access the outer a from inside the block.
The same TDZ applies to exports that haven't been initialized yet.
You'll only have this problem with top-level code in a module, since the module graph will be fully resolved before any callbacks occur. Your code in b.js would be safe using a.b in a function called in response to an event (or called from a.js).
If you needed to use a.b in b.js at the top level, you'd need to run the call after b.js's top-level code execution was complete, which you can do via setTimeout or similar:
setTimeout(() => {
console.log(a.b); // Shows the `b` module namespace object's contents
}, 0);

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.

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;

Undefined variable with & without object name in JS

When I run the following simple Javascript line...console logs an error.
console.log(abc);
But when I run the above line like
console.log(this.abc); or console.log(window.abc);
the console does not throw an error. Instead it prints "undefined".
Why?
Note: 'abc' variable is not defined or declared.
It's not errornous to refer to a non-existent property, but it is erronous to refer to a standalone variable name which doesn't exist.
When one references a non-existent property on an object, undefined will be returned - that's how how things work. But standalone variable names, on the other hand, do need to be defined before referring to them, in almost all situations. Failing to define a standalone variable before trying to refer to it will throw a ReferenceError in all but 2 situations:
(1) When using typeof
console.log(typeof foo);
(2) When assigning to such a variable in non-strict mode (the variable will be implicitly created on the global object):
const arr = [1, 2, 3];
for (i = 0; i < arr.length; i++) {
// if in sloppy mode, and i hasn't been defined beforehand,
// it will be created as a global variable
}
console.log(typeof i);
If you try to assign to such a variable in strict mode when it hasn't been declared beforehand, an error will be thrown.
When you looking for a variable
console.log(abc);
Is not defined and should throw
Uncaught ReferenceError: abc
is not defined in any context at all
however
console.log(this.abc) and console.log(window.abc)
part of it is defined
console.log(this)
console.log(window)
return current objects instance and window objects respectively and have a reference point. the plain console.log(abc) has no reference point
actually it's not a variable. It is a key which is inside an object. (At this point the object is this or window). this and window created from JavaScript.
It's an interesting question to ask!.
To be able to understand this, we must understand Variable Hoisting in Jaascript.
if you want an undefined for a variable that is not declared yet , well the following would work!
console.log(abc)
var abc = 'ss';
So, here if we try to access a declared variable before it is initialized then undefined is actually assigned at the top of function execution. in other words, all the variables are hoisted to the top of their immediate scope level and are assigned undefined until they reach the initialization point.
to give you a blunt answer, trying to use an undeclared variable would give you Reference Error while trying to access a declared variable before the initialization would give youundefined
and, for objects like this and window well they are available objects . so basically at this point people say, In Javascript everything is present, meaning, trying to access a property that is not yet there in a js object would give you undefined.
On most browsers and Javascript environments that respect Javascript specification (this may change in other javascript environments where there is no console):
Any property that has not been assigned a value or non existent property (or a function without a return statement) returns undefined.
In the first examples, the dot operator is being called. The behaviour in case you access a non existent property is defined and it is that to return undefined, which is a valid js type. Therefore undefined is being logged.
On the last example, on the other hand, the non existent variable throws a ReferenceError. This happens directly, as unlike the first case, no context has been accessed, no dot operator is being called and this is just a plain error.
It is the normal JavaScript behavior.
Lets consider a variable name
var name;
console.log(name);
The above wont throw an error, because the variable name was declared before using it in console.log.
console.log(name);
This will throw a reference error because you did not declare the variable first.
But,
If name is accessed from an already defined variable( e.g. window, this ), it wont throw an error whether defined or not. Just like the below will simply print undefined, reference error wont be thrown.
console.log(window.name);
Note that window is already defined by the browser.
Notwithstanding,
console.log(window_a.name);
the above will throw a reference error because name is being accessed on an undefined variable window_a.
var window_a = {};
console.log(window_a.name);
This wont throw reference error because window_a is now declared.

const doesn't work in Edge 15 developer tools

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.

Categories

Resources