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.
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.
First up is partial hoisting. This applies to variables only. Functions are handled differently. So consider the code below:
<a href="https://medium.com/media/9bbbe5b87308be20f623fda44d0eff54/href">https://medium.com/media/9bbbe5b87308be20f623fda44d0eff54/href</a>
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:
<a href="https://medium.com/media/7c3daa274a21c2b92e2235baa705b9a6/href">https://medium.com/media/7c3daa274a21c2b92e2235baa705b9a6/href</a>
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.
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:
<a href="https://medium.com/media/8f7b1c79757c7b8b3c0b91446f8974c6/href">https://medium.com/media/8f7b1c79757c7b8b3c0b91446f8974c6/href</a>
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:
<a href="https://medium.com/media/ce1219f1b51f2bcf56709c70c0f5861a/href">https://medium.com/media/ce1219f1b51f2bcf56709c70c0f5861a/href</a>
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:
<a href="https://medium.com/media/a71ab3b29c71083f07691f9fd0a238eb/href">https://medium.com/media/a71ab3b29c71083f07691f9fd0a238eb/href</a>
This is because Javascript does not hoist function expressions, or arrow functions.
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!
Originally published at ruairidhwm.github.io on March 19, 2019.