Kula.blog

Learn JS. Hoisting (3). Example with scope

Please take a look at the previous posts if you have any trouble following this post:

In this post I want to explain hoisting when we consider it inside new function scope:

  var b = 'outer value';

(function() {
console.log(b); // undefined

var b = 'inner value';
}());

The big new thing is that function has it's own LexicalEnvironment. Let's add comments with Lexical Environments at each important line. Please notice that we have two environments:

  // Module Lexical Environment: { b: undefined }
var b = 'outer value';
// Module Lexical Environment: { b: 'outer value' }

(function() {
// Function Lexical Environment: { b: undefined }
console.log(b); // undefined

var b = 'inner value';
// Function Lexical Environment: { b: 'inner value' }
}());
// Module Lexical Environment: { b: 'outer value' }

Lexical Environment works like explained in the first post Learn JS. Hoisting (1). We didn't talk about all the properties of the Environment, and one of them starts to be important right now outer environment reference. If we start from the Function Lexical Environment, then we can see a list of Lexical Environments ending with the Global Environment. Global Environment is a root and has no outerEnvironmentReference.

// Global Lexical Environment: { 
// // all global properties are stored here
// outerEnvironmentReference: null // no outer environment
// }


// Module Lexical Environment: {
// b: undefined,
// outerEnvironmentReference: <reference to Global Lexical Environment>
// }
var b = 'outer value';
var outerB = 'outer value';

(function() {
// Function Lexical Environment: {
// b: undefined,
// outerEnvironmentReference: <reference to Module Lexical Environment>
// }
console.log(b); // undefined
console.log(outerB); // 'outer value' - WORKS!

var b = 'inner value';
}());

If an identifier is not found in the current Environment, then outerEnvironmentReference is used to look for it up the tree. Thanks to that, we can use outerB inside the function. If the identifier is found, then the Lexical Environment returns its value. This is the tricky part as b it returns undefined. It can be unexpected to see it before the var declaration.

If you remember about Temporal Dead Zone from my previous post, then you may be wondering what will happen if you use let or const?

  let b = 'outer value';

(function() {
// start TDZ for b

console.log(b); // < uninitialized > - throw Reference Error

let b = 'inner value'; // declaration ends TDZ for b
}());

Unfortunately b is shadowed from the beginning of the function and you can't access it before its declaration.

// Function Lexical Environment: { 
b: < uninitialized > - throw Reference Error,
// outerEnvironmentReference: <reference to Module Lexical Environment>
// }

I hope that it makes more sense now. If you have any questions feel free, respond directly to the newsletter email.


← Home