3

function b() {
    function a() {
        console.log(x);
    }
    a();
    const x = 10;

}
const x = 20;
b()

If I understand lexical scoping and execution context correctly, when function a() is invoked, it should have a reference to b's lexical environment. During the execution of a(), the x const inside of b() is inside of the temporal dead zone until a finishes executing. However, shouldn't a() recognize that there is no x value to access (since it's in the TDZ) and thus should it not be searching through the global lexical environment to find that const x = 20?

in the above case, a() throws a reference error.

Nuro007
  • 157
  • 12
james
  • 550
  • 8
  • 14
  • I'm not following your question, everything here is happening as it should. What's exactly your question? Do you think the error should not be thrown? – Gerardo Furtado Dec 07 '22 at 03:42
  • @GerardoFurtado I'm not exactly sure how else to word the question sorry. Just wondering why function a() didn't look into the global lexical environment for const x. I've read that const/let are put inside the "TDZ" where it is inaccessible until it is initialized during execution. However, since a() is invoked before the x inside of b() is initialized, i figured it would look through the scope chain to find x. Consider the case where I delete const x inside b, it will console log 20. – james Dec 07 '22 at 03:47
  • 1
    if move `const x = 10` to above `a()`, it logs 10. – Layhout Dec 07 '22 at 03:50
  • @Layhout thanks but the question wasn't about getting the intended output. – james Dec 07 '22 at 03:53
  • It looks like the lexical scope of `const x = 10;` is inside `b()`, and overriding the outer level declaration. You get an error since you reference it before declaration. – Peter Thoeny Dec 07 '22 at 03:55
  • Referring to different variables depending on execution state is exactly what *lexical scoping* is **not** – Bergi Dec 07 '22 at 13:10
  • @Bergi so my understanding of lexical scoping is that the location of where the function is defined determines it's outer lexical environment when invoked. is that correct? so when a() is invoked, it will have a reference to b()s lexical environment. – james Dec 07 '22 at 16:51
  • "*the location of where the function is defined determines it's outer lexical environment*" - yes. Only the definition. The "*when a() is invoked*" doesn't really matter, it may be before `const x = 10;` is initialised or after, the scope inside `a` is the exact same. – Bergi Dec 08 '22 at 01:45

1 Answers1

1

To explain what's happening internally, when b() is called a new environment record (ER) is created. Conceptually, the "ER" represents a "scope" and is responsible for holding the bindings (variables) which are created within the b function. The ER can have a binding created within it but no value associated with it, so a binding can be instantiated but not initialised to a value (due to this, the binding is in the TDZ). In the case of b, an unitialised binding for x is created within b's ER before any of the code within b has actually run. It is only once the code within b() starts running and we reach the line const x = 10; does the x binding become initialized with a value. That means that even though you're calling a() before the line const x = 10; is evaluated, the x binding has already been created, just without a value (this is why x is "hoisted")

When you try and access x from within a, a's scope (ie: its ER) is first checked for x. As x isn't defined within a, we walk up the scope chain to the surrounding scope to search that for x. As the surrounding scope is b's scope, b's ER is checked for the x binding, which as explained above, it has. That's why we stop searching the scope chain when we check b's scope, as we've already found a binding for x, and thus we never check the global scope for its x binding. However, as the binding for x that we found within b's ER has no value and is uninitialised, JavaScript will throw a ReferenceError.

Nick Parsons
  • 45,728
  • 6
  • 46
  • 64