Variable declaration and hoisting

Javascript Baseline pt. 1

Photo by Joshua Aragon on Unsplash

Recently I’ve dived into ECMA-262 (9th Edition / June 2018) specification and in this article I will try to explain variable hoisting using LexicalEnvironment and VariableEnvironment. This article mainly summarise answers to several StackOverflow questions and my understanding of the specification.

The difference between var and let declarations is scoping. var is scoped to the nearest function block and let is scoped to the nearest enclosing block, which can be smaller than a function block. Both are global if outside any block. Also, variables declared with let are not accessible before they are declared in their enclosing block.

Let’s review following interview task:

var x = function() {
return a;
var a = 1;
function a() { return 'hello'}
}

To answer what value will be returned, we should understood LexicalEnvironment and VariableEnvironment concepts from the ECMA-262 specification.

A lexical environment consists of an environment record, which can be thought of as an object whose properties are the variable and function names declared within the associated execution context. It also has, for functions, identifiers from the formal parameter list in the function declaration or expression (e.g. function foo(a, b){} effectively declares a and b as variables on foo's environment record).

A variable environment is just the part of a lexical environment within the execution context, essentially just the variables and functions declared within the current context.

Now we could easily rewrite code in terms of Environments using knowledge that we earned from the specification:

LexicalEnvironment:
outer: VariableEnvironment
VariableEnvironment:
a = function () { return 'hello' }
outer: global

So when we enter function we could initialise VariableEnvironment with undefined as there is var declaration, but we haven’t reach it yet, or with function definition. And of course functions definition will win over undefined.

Let’s look at another interview question:

for (var i = 0; i < 5; i++) {
setTimeout(() => {
console.log(i + i + '' + i)
}, i*10)
}

I was expected output:

00
21
42
63
84

But got:

105
105
105
105
105

You may already know that Javascript is single-threaded language. So it will finish loop as it first in execution context and only then will switch to tasks created by setTimeout method. So we could expect something like this from original code:

LexicalEnvironment:
outer:
LexicalEnvironment:
outer: VariableEnvironment
LexicalEnvironment:
outer: VariableEnvironment
LexicalEnvironment:
outer: VariableEnvironment
LexicalEnvironment:
outer: VariableEnvironment
LexicalEnvironment:
outer: VariableEnvironment
VariableEnvironment:
i = 5
outer: global

When loop finished we started tasks with the value of i = 5, that’s why we have same result in every console.log statement.

This code was given with remark that I should use closure to make it work. Wikipedia gives plain and simple definition:

A closure is a record storing a function together with an environment.

To make it even simpler, closure is javascript function that have access to its own variables and all other variables declared outside in parent function definition or even globally.

So I’ve rewrite it with closures in mind:

for (var i = 0; i < 5; i++) {
let closure = (j) => () => console.log(j + j + '' + j)
setTimeout(closure(i), i*10)
}

It gives us the result as we wanted:

LexicalEnvironment:
outer:
LexicalEnvironment:
j = 0
outer: VariableEnvironment
LexicalEnvironment:
j = 1
outer: VariableEnvironment
LexicalEnvironment:
j = 2
outer: VariableEnvironment
LexicalEnvironment:
j = 3
outer: VariableEnvironment
LexicalEnvironment:
j = 5
outer: VariableEnvironment
VariableEnvironment:
i = 5
outer: global

But is there any simple solution? There is! Just change var to let:

for (let i = 0; i < 5; i++) {
setTimeout(() => {
console.log(i + i + '' + i)
}, i*10)
}

And in terms of Environments it will look like:

LexicalEnvironment:
outer:
LexicalEnvironment:
i = 0
outer: global
LexicalEnvironment:
i = 1
outer: global
LexicalEnvironment:
i = 2
outer: global
LexicalEnvironment:
i = 3
outer: global
LexicalEnvironment:
i = 4
outer: global

Understanding difference between var and let will help you to write better code with less complexity. Understanding Environments will help you to crack almost every question about hoisting and closures, because if you will be able to decompose every code blocks in terms of Environments you’ll see the right answer from it.

Hope this article and question links in it help you to get better understanding in this complex yet essential Javascript concepts. Feel free to left comment or private response if you have any remarks about the article and clap (you could left up to 50 claps!).