A basic introduction to JavaScript variable definitions and hoisting
Follow me on Twitter for JavaScript tips and book announcements.
I wrote this tutorial as an introduction to an often skipped subject. I can’t blame you, JavaScript is an accessible language — just pop open your text editor and you’re ready to start coding. But JavaScript has traditionally been a language of many quirks, oddball features and inconsistencies. Perhaps this article will be helpful to those who are completely new to the language.
First things first. Scope is simply the area enclosed by {} brackets.
There are 3 main types of scopes that provide distinct behavior: global scope, block scope and function scope. Each scope expects different things and has unique rules when it comes to variable definitions.
Event callback functions technically follow the same rules as function scope, they just take the execution context with them to execute at a later time (when the event is completed.)
Before we can see what the actual differences are we need to take a look at some basic variable definition rules. This way we won’t get mixed up later down the road when variable visibility is visualized later in this tutorial.
Variable Definitions
Case-Sensitivity
Let’s just get this out of the way first. Variable names are case-sensitive. This means a and A are two different variables, regardless which keyword (var, let or const) that was used to define them:
Definitions
Variables can be defined using var, let or const keywords.
Of course, if you tried to refer to a variable that wasn’t defined anywhere, you
would generate a ReferenceError error ”variable name is not defined”:
We have to start somewhere — so let’s use this setup to explore variable definitions using var keyword and hoisting.
Prior to let and const, the traditional ES5 model allowed only var definitions:
The variable is defined in global scope once, but it automatically becomes available for use in an inner block-scope. This is just how global scope works.
Hoisting is limited to variables defined using var keyword and function names. Variables defined using let and const are not hoisted and their use remains limited only to the scope in which they were defined.
Likewise, variables defined in global scope will propagate to pretty much every other scope defined in global context, including block-level scope, for-loop scope, function-level scope, and event callback functions created using setTimeout, setInterval or addEventListener functions.
But what happens if we define a variable inside a block scope?
Variable name is hoisted to global scope — you can use it there now.
Except that the value of the hoisted variable is now undefined — it did not retain its original value. Only its definition was hoisted. In other words, only its name. Think of hoisting like a safety feature. But try not to rely on it when writing code. It just prevents reference error bugs.
You may not retain the value of a hoisted variable in global scope, but you will still save your program from generating an error and halting execution flow.
Thankfully, hoisting in JavaScript is automatic. When writing your program more than half of the time, you won’t even need to think about it.
Function Name Hoisting
Hoisting also applies to function names. But variable hoisting always takes precedence. We’ll see how that works in this section. You can call a function in your code, as long as it is defined at some point later:
Note that the function was defined after it was called. This is legal in JavaScript. Just make sure you understand that it happened because of function name hoisting:
It goes without saying if the function was already defined prior to being called, there’d be no hoisting and everything would still work as planned.
Unlike variables, functions don’t have a value. They have a body that will be
executed, when the function is called by its defined name.
However, it is possible to assign a function to a variable name:
This valid JavaScript code will not produce a function redefinition error. The
function will be simply overwritten by second definition.
Even though fun() was a function, when we created a new variable fun and
assigned another function to it, we rewired the name of the original function.
Having said this, what do you think will happen if we call fun() at this point?
Which function body will be executed?
The second one!
You might think that the following code will produce a redefinition error:
However, this is still perfectly valid code — no error is generated. Whenever you have two functions defined using function keyword and they happen to share the same name, the function that was defined last will take precedence.
In this case if you call fun(); the console will output the second message:
This actually makes sense.
In following scenario variable name will take precedence over function definitions even if it was defined prior to the second function definition with the same name:
And now let’s call fun() to see what happens in this case:
But this time the output is:
You can see the order in which JavaScript hoists variables and functions.
Functions are hoisted first. Then variables.
Defining Variables Inside Function Scope
At this point you might want to know that variables defined inside a function will be limited only to the scope of that function. Trying to access them outside of the function will result in a reference error:
It doesn’t matter if variables inside a function were defined using var, let or const keywords. None of them will be visible in global scope.
const
The const keyword is distinctive from let and var.
It doesn’t allow you to re-assign a previously defined variable to a new value:
It’s still possible to change values of a more complex data structure such as Array or objects, even if variable was defined using const. Let’s take a look!
const and Arrays
Changing a value in the const array is still allowed, you just can’t re-assign the object to a different array:
You just can’t assign a new value to the original variable name. Once array always array. Or once an object always an object:
const and Object Literals
When it comes to object literals, the const only makes the definition constant.
But it doesn’t mean you can’t change values of the properties assigned to a variable that was defined with const:
It makes sense because you can’t reassign const to a new variable.
But you can still change its property values if its an array or an object literal.
Let’s review…
Do not use var – it’s with us only to support legacy code.
(Variable hoisting protects from errors but makes value undefined.)
Do use let to define most of your variables.
Do use const to define PI, speed_of_light, tax_rate and other constant values you know shouldn’t change throughout the lifetime of your application.