I’m continuing my series of JS interview questions. These posts are proving very good practice for the interviews I’m doing!
Explain “hoisting”
Hoisting is when a JS declaration is lifted (“hoisted”) to the top of its scope by the JS interpreter. What this really means is that a variable or function isn’t necessarily declared where you think it is. Understandably, this can cause problems. Variables and functions are hoisted differently, as we'll see below.
Hoisting variables
We'll start with an example:
1
2
3
4
5
6
7
8
// Code saved in file:
function containsHoisting() {
console.log(hoistedVariable);
var hoistedVariable = "I was hoisted!";
}
containsHoisting(); // logs undefined
Wait, how did hoistedVariable
get to be undefined? Surely it should be undeclared since we haven’t hit var hoistedVariable
yet.
It’s because of hoisting! You see, although I wrote the code in the example above, the JS interpreter changes it to this:
1
2
3
4
5
6
7
// What the interpreter changed it to:
function containsHoisting() {
var hoistedVariable; // <-- this line here!
console.log(hoistedVariable);
hoistedVariable = "I was hoisted!";
}
That new line is hoistedVariable
getting hoisted up to the top of it’s scope. So it’s now declared, but not defined.
Here’s a more complex example (inspired by Adequately Good)
1
2
3
4
5
6
7
8
9
10
var hoistedVariable = 1;
function scopingFunction() {
if (!hoistedVariable) {
var hoistedVariable = 10;
}
return hoistedVariable;
}
scopingFunction(); // returns 10
What?! How can it return 10?
Tangent about scopes
I was surprised about this myself until I understood JS scoping better, here’s how it breaks down:
In Javascript scopes are defined at function level. Many other languages define scope at a block level (as in an
if
block orfor
loop). This is an important difference to remember.
Thus...
Back to the main event
The code above gets rewritten in the JS interpreter to look like this:
1
2
3
4
5
6
7
8
9
10
11
var hoistedVariable = 1;
function scopingFunction() {
var hoistedVariable; // <-- this line here!
if (!hoistedVariable) {
hoistedVariable = 10;
}
return hoistedVariable;
}
scopingFunction(); // returns 10
Note that the global hoistedVariable
gets completely overwritten by the local hoistedVariable
as declared in scopingFunction
. So at the point of the if
conditional hoistedVariable
is undefined
and not 1.
Function hoisting
Hoisting functions works differently than variables. Since a function is declared and defined at the same time the function definition is hoisted along with the function name.
Since examples make things clearer:
1
2
3
4
5
6
7
8
function containingFunction() {
var hoistedVariable = 2 + 2;
function hoistedFunction() {
return hoistedVariable;
}
return hoistedFunction();
}
containingFunction() // returns 4
Hopefully that example wasn’t surprising. But just to better understand what’s going on, here’s how the JS interpreter rewrote things:
1
2
3
4
5
6
7
8
9
10
11
12
function containingFunction() {
// this is the hoisted section
var hoistedVariable;
function hoistedFunction() {
return hoistedVariable;
}
// here's the rest of the code
hoistedVariable = 2 + 2;
return hoistedFunction();
}
containingFunction() // returns 4
Notice that the entire hoistedFunction
gets moved up, while only the declaration for the hoistedVariable
is hoisted.
Let’s try with a more complicated example:
1
2
3
4
5
6
7
8
function containingFunction() {
var hoisted = "I'm the variable";
function hoisted() {
return "I'm the function";
}
return hoisted(); // results in a TypeError
}
containingFunction()
But wait, the hoisted
function is defined right there, what gives?
Because functions are hoisted after variables, naming conflicts can happen. Again, let’s look at what the JS interpreter wrote for this code
1
2
3
4
5
6
7
8
9
10
11
12
function containingFunction() {
// hoisted section
var hoisted;
function hoisted() {
return "I'm the function";
}
// rest of the code
hoisted = "I'm the variable";
return hoisted();
}
containingFunction() // results in a TypeError
As you can see, the function definition for hoisted
is overwritten by the variable definition ("I'm the variable"
) which appears lower down in the interpreter’s version of the code. Yet another reason why good names are important!
Resources
- JS is sexy
- Adequately Good (which I think is a hilarious name for a coding blog!)