Does JavaScript Function Order Matter?
The readability of a program is essential for developers to able to understand code that isn’t their own. Having descriptive variable names, functions, and files goes a long way to ensuring future readability.
When thinking about the order of functions in JavaScript, the following questions often come to mind.
- Should the order be alphabetical?
- Should the order be in terms of types of functionality?
- Should it be in the order of the desired execution?
While these types of questions are certainly relevant they don’t correctly question the actual practical aspects of function order. So, why exactly does JavaScript function order matter?
Well, function order matters a huge amount when a variety of functions exist inside multiple scopes of a program. For a JavaScript program to perform correctly, functions in the inner scope must be able to access functions in the outer scope.
This is achieved through the concept of hoisting. The concept of hoisting can be initially confusing for new JavaScript programmers, rather than functions and variables being available after their declaration, they can be available to the JavaScript interpreter (engine) beforehand.
Hoisting is an important concept during the creation phase of a JavaScript program. The first time the JavaScript interpreter scans through a file, it will look for variables and functions.
Once found, the variable or function name is assigned to memory. However, there is a distinct difference between the two types of hoisting used for a function and a variable.
Variables are only partially hoisted, this means that the name of the variable will be saved in memory but its initial value will be set to undefined.
In comparison, functions are fully hoisted, meaning that during creation phase functions are fully assigned a location and value in memory. This is important because it allows a function to be called before its defined.
This can be clearly demonstrated in the following code snippet:
f1(); function f1() { var a = 1; secondFunction(); }
If we are to read this file line by line, our function f1
is clearly called before its been properly declared. However, thanks to hoisting, JavaScript has scanned through the file and the function f1
has already been put into our local scope.
This can be confirmed, by running the snippet in Chome dev tools and setting a breakpoint on the line after f1
.
On the right-hand side, we can indeed see that local scope has the reference to the f1
function even though it has been declared at the bottom of the snippet.
This reassures us that the order that a function has been declared does not necessarily matter when dealing with code that resides in the same local scope. Due to the mechanics of hoisting, a function in a singular scope can be both declared and invoked in any order.
Remember, hoisting will only occur for function declarations and not function expressions.
Depending on how the functions are defined, they fall into one of two categories, function declarations and function expressions.
The JavaScript compiler will process function declarations upon entry into an executable context e.g the global scope or a specific function call.
Once these functions are processed any subsequent calls of these functions will be able to be invoked correctly regardless of the order of when they are called.
For example:
// Outputs: "Hoisted!" hoisted(); // TypeError: undefined is not a function notHoisted(); function hoisted() { console.log("Definition hoisted!"); } var notHoisted = function() { console.log("Not hoisted!"); };
We can see that the function declaration will successfully be hoisted and the interpreter will have a reference and be able to call the function at runtime.
In contrast to this, the function expression will not be hoisted, therefore the interpreter has no reference to the function at runtime. Hence, the error:
// TypeError: undefined is not a function
But the reliance of this behavior is restricted to the confines of a singular scope in the call stack. The order of functions starts to matter hugely when functions are declared and invoked inside other functions (these are also called closures).
Javascript Function order of Execution
Now that we have established that function order matters, we can analyze the sequence in which these functions get executed. We already know that the process of hoisting assists in the order of execution of multiple functions.
But when exactly does hoisting occur? Well, before executing a file, the JavaScript interpreter/compiler will assess will if it needs to hoist a function into the global scope.
Two main factors determine when hoisting of functions to occur.
They are:
- How the functions are defined
- When the functions are called
Let’s take the following example:
var a = undefined; function firstFunction() { secondFunction(); } function secondFunction() { return a; } firstFunction();
Now, before this code is executed the JavaScript interpreter will process all function declarations i.e (firstFunction and secondFunction) before any other code in the context is executed.
The order of this process looks something like this:
- An empty object is created for the execution context.
-
{ }
- For every variable and function declaration in the context, they are added as property into the empty object with an initial value of undefined.
-
{ firstFunction: undefined; secondFunction: undefined; }
-
- These function declarations are then processed and assigned correctly:
-
{ firstFunction: firstFunction(); secondFunction: secondFunction(); }
-
The firstFunction
is defined and then assigned to the property object
- The
firstFunction();
is executed, calling thesecondFunction()
function. - The code inside
firstFunction
callssecondFunction
, which it gets from the variable object in the current scope.
But we still have a problem here, because a
is undefined:
This is because the variable a is defined within the scope of firstFunction
so when it is referenced in secondFunction
is unable to access its original declaration.
This is rectified by changing the order of the functions and putting secondFunction
inside firstFunction
. This is an example of a basic JavaScript closure and also highlights the importance of JavaScript function order.
function firstFunction() { function secondFunction() { return a; } var a = 1; secondFunction(); } firstFunction();
Where should JavaScript functions be placed?
When dealing with a simple web page or a small snippet of HTML it’s important to determine where the best place is to place a function.
While this situation might seem trivial, there are plenty of occasions where one has to deal with a client’s website that has been bloated with external JavaScript libraries and code snippets.
These types of scenarios create a delicate ecosystem where the insertion of a function that conflicts with a namespace of a different scoped function can block the execution of JavaScript.
This often results in a website’s main functionality ceasing to work that can cause lost traffic and revenue. In order to combat this, it’s important to know where exactly to place a function that won’t interfere with existing functionality.
Generally, JavaScript in the head of the document may block the rendering of the page until the file is fully loaded. This is far from ideal and causes an unnecessary amount of waiting.
A function may rely on the logic inside a ready event of some sort. So it needs to be placed after the elements it references. When you are unsure of where these dependencies are, place the function just before </body>
element.
Async vs Defer
As previously stated, adding a function or snippet of JavaScript into an existing project has its risks. Particularly when it comes to function namespacing and the reliance on other events.
Thankfully there are data attributes that support these types of more complex situations.
Let’s say we create a script with the following function:
function manipulateDom() { const mainText = document.querySelectorAll('.mainText'); mainText.style.color = 'red' } manipulateDom();
The function we are adding relies on a fully parsed DOM so the defer attribute should be used:
<script defer src="script.js" >
Because our manipulateDom
function requires interaction with the DOM. We need the DOM to be fully parsed before its executed.
We can ensure that this is the case by placing the script at the end of the HTML page, however, this doesn’t always make sense in terms of readability of the page.
The defer attribute tells the browser to execute the script once the document has been fully parsed. The file will be downloaded while the HTML document is still parsing but it will not be executed until the parsing is fully complete.
If our script does not rely on an element in the DOM and is instead self-contained, this is where the async attribute can come in handy. Since we do not care when the file is executed, loading the file asynchronously is the most ideal option.
<script async src="script.js" >
It’s important to keep in mind, that once a requested external JavaScript file is placed right before </body>
element, the use of async or defer attributes are not as important and are likely not required.
Conclusion
So there you have it, there is a surprising amount of depth when it comes to the order of JavaScript functions. The combination of the JavaScript interpreter (engine) and the mechanism of hoisting enables functions to be placed in an order that’s not necessarily sequential i.e not read line by line.
When it comes to function placement, there is even more understanding needed of closures so if your looking for more information, I’ve covered some aspects of closures in a previous post, does JavaScript use garbage collection?