TIE: When you hoist, I hoist, we hoist

Joanna Sese
4 min readApr 20, 2018

--

Welcome to TIE: Today I Explain. This is part two in a series on key JavaScript concepts that help js developers become better js developers. Find part one here.

Quick memo: I’m starting with a bunch of basics. My style is conversational. I’ll get into deeper stuff over the lifespan of this blog. If you’re super-advanced, please feel free to drop knowledge in the comments. If I made a mistake, call me out!

Also, if anyone is wondering where the title of this post came from, the answer is yes.

Today we’re talking about ES6 variables, hoisting, scopes and a dash of mutability.

In Javascript, we declare variables using the following keywords:

varletconst

The keyword var may be more familiar to most. let and const were introduced when ES6 came around. Before that, var was the only game in town. You may have been told that var and let are interchangeable. They are not.

So yea, let’s talk about hoisting.

Hoisting with var

When using var, variable declarations are hoisted to the top of their scope. Consider the following examples:

// EXAMPLE 1
console.log(puppy);
var puppy = "Pancake";
console.log(puppy);
// is the same asvar puppy;
console.log(puppy);
var puppy = "Pancake";
console.log(puppy);

In the first example, var puppy = “Pancake”; is an expression. The expression is not hoisted to the top of the scope. You know what is hoisted, though? The variable declaration! If you’re having trouble with declarations versus expressions, you could practice re-writing them in your head (like in the second section of example 1) until you don’t need the training wheels anymore.

What prints?

If you said “undefined” and “Pancake,” you’d be correct! The first time console.log is called, the machine only has knowledge of var puppy, which has not yet been assigned a value. The second time console.log is called, our variable has been given the value of “Pancake” and therefore the machine prints “Pancake”.

Hooray!

Okay, just for funsies, quick scopes review. What happens here?

// EXAMPLE 2
console.log(puppy);
let puppies = () => {
console.log(puppy);
var puppy = "Pancake";
console.log(puppy);
}

Ahh!

ReferenceError: puppy is not defined

That first console.log in the global scope doesn’t have access to the variables in the puppies function. If we comment out that first line, the machine will print “undefined” and “Pancake” as we anticipated.

So now we understand how hoisting works with var.

In a case like this, if we replaced var with let, the outcome would be the same. That’s why sometimes people say that they are interchangeable. Sure, in this case, they are. But keep reading.

Scopes with let

// EXAMPLE 1 WITH VAR
for (var i = 0; i < 5; i++) {
setTimeout(function() { console.log(i); }, i * 1000 );
}
// prints 5, 5, 5, 5, 5
// EXAMPLE 2 WITH LET
for (let i = 0; i < 5; i++) {
setTimeout(function() { console.log(i); }, i * 1000 );
}
// prints 0, 1, 2, 3, 4

In the first example where var is used, each function inside the loop executes only once the loop is completed. Here, var i is one variable, and the function within setTimeout refers to that variable. When the loop completes, var i will always equal to 5. Confused? Check this out.

// var i; <-- invisible but it's therefor (var i = 0; i < 5; i++) {
setTimeout(function() { console.log(i); }, i * 1000 );
}
console.log(i); // logs 5;

With var, declarations are hoisted to the top. In the example above, there’s an invisible var i before the for loop. Again, every time the loops runs, var i = 5. The function inside the for loop references var i, so it prints 5.

In the second example, it’s the same code except we use let. This tiny ES6 update prints out an entirely different answer. Consider this code:

for (let i = 0; i < 5; i++) {
setTimeout(function() { console.log(i); }, i * 1000 );
}
console.log(i); // ReferenceError: i is not defined

With let, there’s no invisible variable declaration before the for loop. Declarations aren’t hoisted to the top! When we try to console.log outside of the for loop, we receive a reference error because the machine doesn’t have anything to reference.

Why’s this matter? It matters because let is block scoped, while var ignores the block scope. let i is not accessible outside of the block scope, which in this case is the for loop. Here, let i is defined within the block scope, and it doesn’t reach out to the global scope the way var does.

As a result, let i acts differently. As we iterate over the loop, let i = 0, prints 0. Then let i = 1, prints 1. And so forth.

Okay, now run this:

for (const i = 0; i < 5; i++) {
setTimeout(function() { console.log(i); }, i * 1000 );
}

Rude.

Mutability

Const is immutable That means it’s unchangeable. If you assign const a value, you can’t reassign it later.

const northernStar = "Polaris";
northernStar = "Betelgeuse";
console.log(northernStar);
// SyntaxError: Assignment to constant variable: northernStarlet northernStar = "Polaris";
northernStar = "Betelgeuse";
console.log(northernStar); // "Betelgeuse"
var northernStar = "Polaris";
northernStar = "Betelgeuse";
console.log(northernStar); // "Betelgeuse"

In typical JavaScript fashion, though, const isn’t completely immutable. You can change its properties.

const outfit = {pants: 'maybe'};
outfit.pants = 'not today';
console.log(outfit) // { pants: 'not today' }

--

--

Joanna Sese

San Gabriel Valley → Orleans Parish. Web developer. Clean code, full hearts, can't lose.