copenndthagen
copenndthagen

Reputation: 50732

Hoisting in ES6 with respect to `let` and block scope

When I run the following code written using ES6 let:

function doSmth(){
  age=27;
}
let age;
doSmth();
console.log(age);

I get the output correctly as 27.

Now I am a bit confused as to how this works. Specifically, I have read is that let has a block scope.

So, how is it that let age, which is declared outside the function doSmth, is the same variable inside the function doSmth? Is that something to do with global scope, but if yes, then what role does block scope have exactly ?

Upvotes: 1

Views: 390

Answers (4)

CertainPerformance
CertainPerformance

Reputation: 370679

"Let has block scope" doesn't mean that the variable must be defined in the same block - it means that the level at which the variable is defined can be a block.

In contrast, the level at which a var variable is defined cannot be a block, unless that block is also a function block. If a var variable is initialized inside a non-function block, it will be hoisted out to the nearest outer block which is also a function block.

No matter whether a variable is declared with var, let, or const, it will be referencable anywhere else inside the same block in which it was declared, including inside child blocks. (For let and const, the line that declares the variable, eg const foo =, must also have run for a reference to foo to work without throwing.)

Another way of looking at it is: if a variable is referenced somewhere, the interpreter looks for whether it's initialized in the current block. If so, that's the binding that's used. Otherwise, it looks in the next outer block to see if the variable is initialized there - if so, that's the binding that's used. And so on. If it gets all the way to the top level, and no binding has been found, and it's not a property of the global object, that reference will throw a ReferenceError.

In your code, the age variable happens to be global, since it's defined on the top level, but that doesn't really matter - the code would work the same if age was just an an outer block, but not on the top level, eg:

function foo() {
  function doSmth() {
    age = 27;
  }
  let age;
  doSmth();
  console.log(age);
}

foo();

Upvotes: 3

jfriend00
jfriend00

Reputation: 707218

Now I am a bit confused as to how this works. Specifically, I have read is that let has a block scope.

Yes, let has block scope. But a block such as inside your doSmmth() function has access to ALL the variables in parent scopes that have been defined or hoisted at the time of the function execution.

So, let age; is in the parent scope and has been defined by the time doSmth() executes. Therefore the interpreter tasked with looking up a variable named age finds it in the parent scope just fine.

So, how is it that let age, which is declared outside the function doSmth, is the same variable inside the function doSmth?

Here's how the interpreter works in this regard. Your code executes in these steps:

  1. Parse the code you've presented.
  2. The function doSmth() definition is encountered. Functions are hoisted to the top of the containing function scope so that definition is immediately added to this scope object. let age is also encountered in the parsing and is added to the parsed scope, but it is marked in a way that it will not yet be accessible to code.
  3. Start executing the code. A new scope object is created from the parsed code.
  4. let age; is encountered. This marks the age variable which was previous put into this scope at parsing time as now available for code to use.
  5. Call doSmth(). This pushes a return address on the callstack and as it sets up to execute doSmth(), it creates a new function scope.
  6. It then executes age = 27 inside that function. The interpreter looks in the local scope for a symbol named age. It doesn't find one. So, it goes up the scope chain and looks in the parent scope. It finds a matching symbol and uses that one.
  7. The function returns which pops a return address off the call stack and resets the current scope back to the parent scope and the execution goes to right after where doSmth() was and executes the console.log(age).
  8. The interpreter looks in the local scope for a symbol named age and finds it and uses it.

So, the main keys here that it sounds like you may be confused by are:

  1. If a symbol isn't found in the local scope, then the parent scopes are searched for the symbol name.

  2. let does create a block-scoped symbol, but it can still be accessed by any child functions also declared in that scope.

  3. In this particular code, there wouldn't be any difference between let and var and it isn't hoisting that makes this work. It's parent scopes.

Upvotes: 1

Akshay Bande
Akshay Bande

Reputation: 2587

Your code will go through two phases.

  1. Compilation where all hoisting will occur (var declared variables).
  2. Execution where actual execution of code will occur.

In first phase you will declare function doSmith and age variable. Before executeion phase JS engine knows what is age variable and what is doSmith.

In next phase i.e. execution phase, execution will occur,

doSmth();
console.log(age);

will get executed. Hence in age variable is accessible to function doSmith in execution phase.

Upvotes: 0

Vatsal
Vatsal

Reputation: 2128

In your code, both doSmth and age are in global scope. Windows for browser and module object for node. So they basically are in the same scope.

Upvotes: 0

Related Questions