Hoisting in Javascript
Recently I’ve been working on understanding more Javascript fundamentals and I took a look at hoisting. I thought that it was a pretty cool concept, so have written a blog post explaining it.
What is hoisting?
Hoisting is a process in the Javascript engine which does a pass through your code and allocates memory based on the presence of certain things. Hoisting is a way for us to understand how this works.
In the Global Execution Context, there are two stages, the creation stage, and the execution stage. The former provides the Global Object (e.g. ‘Window’ in the browser), the this
keyword, and hoisting. The latter is when we run our code.
When the Javascript engine does its pass through your code, it looks for function declarations (but not arrow functions, or function expressions), and variables (excluding let or const). These are then ‘hoisted’ which allocates them space in memory.
Partial Hoisting
First up is partial hoisting. This applies to variables only. Functions are handled differently. So consider the code below:
console.log(favouriteDrink) // undefined
var favouriteDrink = "coffee"
console.log(favouriteDrink) // coffee
So on the first line we try and console.log
our favouriteDrink
variable. There’s a problem here though - we haven’t defined it yet. So really we should get a Reference Error right? Where is undefined
coming from?
Well, when the Javascript engine passes through the code above, it sees that we have a var
and realises that a variable is going to be defined. It doesn’t care what the variable is, but it knows that some memory is going to be needed, so it allocates some in the heap so that it’s ready when we need it.
This is why you then get ‘undefined’ in the example above. You’ve probably seen this when coding before right? Well it’s just Javascript’s placeholder. It won’t break the script, but it knows that something will be allocated to our favouriteDrink
variable.
Great, so how about a trickier example? Look at the code below:
console.log(favouriteDrink) // undefined
var favouriteDrink = "coffee"
var favouriteDrink = "beer"
console.log(favouriteDrink) // beer
Ok, so we have two variables with the same name, but different values. How does Javascript handle this? It doesn’t care. For the purposes of hoisting, the engine sees the first variable declaration and assigns it some space in the memory heap. It then sees a second declaration of the same variable, but the value of that variable isn’t significant. The memory has already been assigned, so the engine simply ignores it.
When you run the code above, our second console.log
statement predictably returns beer, as we’re taking the latest declaration of the variable.
As a note, remember that the following won’t work:
const a = "Foo"
let b = "Bar"
Neither const
not let
get hoisted.
Full Hoisting
Function declarations (but not expressions, or arrow functions) are fully hoisted. This means that the function is allocated space in the memory heap, but instead of simply creating a placeholder like we saw in the previous section, the contents of that function are stored in memory. So for an example:
favouriteDrink() // My favourite drink is coffee!
function favouriteDrink() {
console.log("My favourite drink is coffee!")
}
As we can see, we call the function before we have actually written it. So what’s going on here? Well, the contents of the function have been stored in memory, and so Javascript knows what you’re looking for. Imagine that the function had actually been taken from where we have written it, and instead been placed at the top of the file. That’s generally an easy (albeit simplistic) way to visualise hoisting. Instead, the memory is assigned into the memory heap.
So let’s look at a more complex version:
var sport = "climbing"
function favouriteSport() {
console.log(`Before, my favourite sport was ${sport}`) // Undefined
var sport = "running"
console.log(`Now, my favourite sport is ${sport}`) // Now, my favourite sport is running
}
favouriteSport()
Do you understand why we get undefined in our first console.log
and running
in our second? Well, hoisting happens on every execution context. Any time you run a function in Javascript, a new execution context is created and we have to go through the creation and execution stages in the Global Execution Context again.
So when we call favouriteSport()
we create a new execution context and hoisting happens. So in our first console.log
we have undefined
because the Javascript engine has created our placeholder. So in the execution stage, we call our function and a new execution context is created. Inside of the execution context, we only have access to the variable inside the function. The variable is hoisted and becomes undefined
until we provide a value to it, in this case - ‘running’.
Then when we call console.log
again, we have a value assigned to our variable and we get our correct output. Make sense?
Please note that the following won’t work:
var foo = function bar() {
console.log("I will not be hoisted")
}
This is because Javascript does not hoist function expressions, or arrow functions.
Conclusion
So that is a very quick introduction to hoisting. Hopefully you found this easy to follow and now understand the concept better. It’s actually pretty simple.
Is hoisting a good thing though? Well, that depends. It’s certainly quite a confusing topic, particularly for beginners and can make code less readable. On the other hand, it’s a quirky feature of Javascript which allows for flexibility in how your code is written.
Personally I would avoid hoisting where I can in favour of more readable code. I tend to use const
and let
over var
anyway and this stops me from (ab)using hoisting by default. I find that if you trigger a Reference Error then you need to look at your code structure and not rely on the internals of Javascript to hopefully figure it out for you.
If you enjoyed this, then I’d recommend checking out Andrei Neagoie’s Advanced Javascript Course - I’ve certainly found it very helpful!
Top comments (0)