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);