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:
Outer Lexical Environment
in this case it's aModule Lexical Environment.
Function Lexical Environment.
// 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.
Module Lexical Environment.
Function Lexical Environment.
// 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.
Want to learn more?
Sign up to get a digest of my articles and interesting links via email every month.