6

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?

2
  • Possible duplicate of "let" keyword vs "var" keyword in Javascript Commented Nov 10, 2016 at 9:25
  • 1
    The declaration of the local x is shadowing the global one, but as you are assigning a value based on an existing x, the compliler cannot find one as one is shadowed and other one is not still declared. Commented Nov 10, 2016 at 9:34

5 Answers 5

6

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

Sign up to request clarification or add additional context in comments.

4 Comments

so what you're saying is that this part let x is evaluated first and the local variable x is created but not declared (so that global x is shadowed), and then this part is executed x || 30 and x is not found?
Yes exactly - that's what happens
thanks, I'm wondering how this process created but not declared is called
I would expect that rather the global name is being erased from a scope - nothing is being created yet
0

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.

Comments

0

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
}

1 Comment

I had a tangent about Lua actually working this way with local, and OCaml having a probably the best-of-both-worlds solution (let and let rec), but then decided against including it.
-1

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

1 Comment

That shouldn't be a problem, inside the function it should use the let x, outside the var
-1

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

1 Comment

Fails for new f().

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.