Adarsh Konchady
Adarsh Konchady

Reputation: 2737

Why can’t a `let` declaration read from the same identifier from an outer scope?

I was playing with some code and ran into a situation where I couldn't identify why let is behaving the way it does.

For the below block of code:

var x = 20; // global scope

function f() {
  let x = x || 30;
}

f(); // VM3426:1 Uncaught ReferenceError: x is not defined(…)

I get the error “x is not defined” on executing f(). I do understand let variables are not hoisted but since x has a global copy, why isn't the line inside function f defaulting to global copy instead of throwing an error?

Does let set the variable to undeclared (instead of 'undefined' with var because of hoisting) at the beginning of the function? Is there any way to get the global copy of x within the function?

Upvotes: 6

Views: 1508

Answers (5)

dumbass
dumbass

Reputation: 27239

Though in JavaScript block-scoped const and let declarations aren’t hoisted to the top of the function, they are still hoisted to the top of the block. As such, once a block is entered, all bindings scoped to that block immediately become available, even those that have not yet been initialized. Accessing a binding between entering its scope and initializing it is a runtime error: the time when this can happen is known as the temporal dead zone. The expression initializing a block-scoped binding is within its temporal dead zone; as such, using the binding identifier in its initializing expression will throw an error.

Although I also sometimes find this design decision disappointing, there are some advantages to it. For example, it allows closures to refer to each other without the need for forward declarations:

const flip = n => {
  if (n > 0) {
    console.log("flip");
    return flop(n - 1);
  }
};
const flop = n => {
  if (n > 0) {
    console.log("flop");
    return flip(n - 1);
  }
};
flip(5);

But it does mean that capturing a previous binding in the initializing expression of a shadowing binding, like let x = 42; { const x = x; }, is impossible.

To access specifically a global binding, you could explicitly refer to a property of a global object, through globalThis, window or such:

var x = 20;                      // global scope

function f() {
  let x = globalThis.x || 30;
  return x;
}

console.log(f());                // 20
x = void 0;
console.log(f());                // 30

However, for shadowing outer bindings in general, you will have to resort to an awkward contraption:

{
  let x = 20;                    // outer, but not global scope
  console.log(globalThis.x);     // undefined (see?)

  function f() {
    let outerX = x;
    {
      let x = outerX || 30;
      return x;
    }
  }

  console.log(f());              // 20
  x = void 0;
  console.log(f());              // 30
}

Upvotes: 0

MSH
MSH

Reputation: 1297

The declaration of the local x is shadowing the global one and new one is still not declared first and cannot be used during initialization. It is same situation like following :

 var foo = 5;
    function do_something() {
      console.log(foo); // ReferenceError
      let foo = 2;
    }
  do_something();

For reference MDN

As suggested by @m.antkowicz you can use window.x in your case.

Upvotes: 0

User
User

Reputation: 1363

you can use this keyword to access global scope

var x= 20; // global scope
 function f() { 
  let x =this.x || 30;    
}
f(); 

but In strict mode this will default undefined

Read it https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/this

Upvotes: -1

m.antkowicz
m.antkowicz

Reputation: 13581

The exception is about the right side x - when you are initializing block-scope x variable the global one is already "forgotten" however the new one is still not being declared and cannot be used during initialization

Compare with explicit calling global one

    function f() {
      let x = window.x || 30;
    }

Also take a look at this MDN article about let dead zones

Upvotes: 6

Gaurav joshi
Gaurav joshi

Reputation: 1799

let allows you to declare variables that are limited in scope to the block, statement, or expression on which it is used. This is unlike the var keyword, which defines a variable globally, or locally to an entire function regardless of block scope.

Here in your case, an exception is thrown because you are defining let with the same name.

For More Info

Upvotes: -1

Related Questions