Functions are the most fundamental building blocks of JavaScript applications. They are the safekeepers of the logic within our programs but also the primitives upon which we build programmatic constructs such as classes and modules.
JavaScript provides different ways to declare and use functions, each with their own nuances and limitations. So given the fact that they are such a fundamental part of the language it is important that you are aware of these characteristics when you are writing JavaScript.
Welcome to the first step in your journey to JavaScript mastery! Let’s get started!
Functions are the Key
Did you know that there are not one but two ways to write functions in JavaScript? Well, now you do. You can either write them as function expressions or function declarations. It will be very helpful for you to understand the difference and the implications of writing functions in either of these two styles because they work in a very different manner that will impact the readability of your code and how easy or hard it is to debug.
This is a function expression:
And this is a function declaration:
As you can appreciate, in their most common incarnations function expressions and function declarations look very similar. That’s why it is especially important to understand that they are actually very different and behave in disparate ways from each other.
Let’s examine each of them in greater detail.
Function Expressions
We use the function expression style whenever we declare a function like an expression, either by assigning it to a variable:
A property within an object:
Or passing it as an argument to another function (like a lambda - note that I am using the term lambda in the sense of function as value):
There are a couple of important considerations you need to take into account when using function expressions like the ones above: they are all anonymous functions - they don’t have a name - and if stored within a variable they are subjected to the same hoisting rules that apply to any other variable in JavaScript. But what is hoisting? And how a function being anonymous can affect our programs?.
W> ### JavaScript Arcana: Function Scope and Hoisting
W>
W> One of the things that confuses us the most when we start learning JavaScript is that while JavaScript looks a lot like C#, in many ways it does not behave like it.
W>
W> These JavaScript Arcana bits will help you understand these hairy parts where JavaScript behaves in a completely different way to what you would expect. You’ll discover them throughout the book as we venture deeper into the world of JavaScript.
W>
W> Oftentimes you’ll see JavaScript Arcana paired with JavaScript Arcana Resolved sections that will provide tips and solutions to help you tackle JavaScript strange behaviors.
JavaScript Arcana: Function Scope and Hoisting
An interesting quirk about JavaScript is that, unlike many other languages, variables in JavaScript have function scope (as opposed to block scope). That is, it is the functions themselves that create scopes for variables and not the blocks. This can be better illustrated by an example:
What happens in this piece of code above is that the JavaScript runtime hoists (moves up) all variables within a function definition to the beginning of the function body. And does it even with variables declared within blocks of code such as if statements. This means that the function that you see above is equivalent to this one:
Where the scope of the treasure variable isn’t only the if statement but the whole function. In yet another example of hoisting the variable i that we declare inside the for loop is actually part of the entire function (Shocker!!):
This is the reason why JavaScript developers - seasoned JavaScriptmancers - usually write all variable declarations at the beginning of a function. They do it in order to be more explicit about what is really happening when a function is evaluated and to avoid unnecessary bugs.
If you take a look at jQuery for instance, a popular JavaScript open source library, you’ll be able to appreciate this technique everywhere:
Functions being the stewards of scope of an application is pretty interesting because, all of the sudden, a function is not only used for encapsulating and abstracting a piece of logic but also for structural reasons. That is, a way to organize and distribute bits and pieces of functionality and code. For instance, you’ll often see functions being declared inside other functions:
This probably looks very weird if you are coming from C#. But up until the recent advent of ES6 modules this was the only way we had to group pieces of loosely related functionality.
JavaScript Arcana Resolved: Variables with Block Scope With ES6 let and const
Happily for you and happily for me ES6 comes with not one, but two ways to declare variables with block scope: the new let and const keywords. From this point forward I invite you to start using let and achieve a more C#-like style of writing code where you declare variables closer to where you use them.
And so, if we rewrite the example we used to illustrate function scope with let, we’ll obtain a very different yet more familiar result:
Now the treasure variable only exists within the if statement block.
Alternatively, you can use the const keyword to declare constant variables with block scope.
Variables declared with the const keyword behave in a very similar way to C#. Attempting to make the constant refer to a different value will cause an exception.
However, it is important to understand that if the constant refers to an object, you can still modify its properties:
This means that const only affects the act of binding a value to a variable, and not the act of modifying that value itself. In order to make an object immutable you need to use Object.freeze but that’s knowledge best kept for another chapter about the beauty of objects. We’ll stick to functions for a little bit longer.
Another aspect of let and const that is interesting is that they do not hoist variables to the top of a block. Instead, if you attempt to use a variable before it has been defined you’ll get an exception (cheers for some sane behavior):
Now that you’ve learnt some of the main characteristics of function expressions let’s take a look at the two types of function expressions that are available to you in JavaScript: anonymous function expressions and named function expressions.
Anonymous Function Expressions
Anonymous function expressions are particularly interesting because even though you read the following:
And you may be tempted to think that the name of the function is castFireballSpell, it is not!?!castFireballSpell is just a variable we use to store an anonymous function. You can appreciate this anonymity by inspecting the name property of the function itself:
Luckily for us, as long as an anonymous function is bound to a variable, the developer tools in modern browsers will use that variable when displaying errors in call stacks (which is a savior when debugging):
Even if we use this function as a argument:
However, an unbound anonymous function will show as completely anonymous within the call stack making it harder to debug when errors occur (I will refer to these functions as strict anonymous function from now on):
This lack of name will also affect the ability to use recursion because a function that doesn’t have a name cannot call itself from inside its body. In spite of that and just like in the previous examples, if we have the anonymous function bound to a variable we take a free pass and can take advantage of JavaScript lexical scope to access the function through that variable:
Notice that this is a pretty brittle way to use of recursion. In this example we are using the variable castManyFireballsSpell to access the anonymous function from within itself. If, at some later point in time, you happen to set the variable to another function you’ll get into a pickle. A tricky situation with a very subtle bug where the original function will call this new function instead of itself (so no more recursion and weird unexpected stuff happening).
A strict anonymous function, on the other hand, has no way to refer to itself and thus you lose the ability to use recursion. For instance, this is the case when we define an anonymous function expression and we invoke it right away:
In summary, the fact that common function expressions are anonymous makes them harder to debug and complicates the use of recursion. And the fact that they are hoisted as variables, a trait common to all function expressions, also makes them less readable as we’ll see in a bit with a larger program as example.
Let’s see some ways to ameliorate or lessen these issues.
Named Function expressions
You can solve the problem of anonymity that we’ve seen thus far by using named function expressions. You can declare named function expressions by adding a name after the function keyword:
The example above shows a variable and a function expression both named castFireballSpell. A named function expression always appears correctly represented in the call stacks even when used as an argument (and not bound to a variable):
This helps while debugging and makes your code more readable since you can read the name of the function and understand what it’s meant to do without looking at its implementation.
An interesting fact about named function expressions is that you cannot call them by their name from the outside:
The name of a function expression is more of an internal identifier that can be used from inside the function body. This is very useful when working with recursion. For instance, if we declare a recursive named function expression and invoke it right away it just works:
In summary, named function expressions improve on anonymous function expressions by increasing readability, improving the debugging process and allowing for a function to call itself.
Function Expressions are Hoisted as Variables
Function expressions are still problematic because they are hoisted like variables. But what does this mean exactly? It means that you can only use a function expression after you have declared it and therefore it forces you to write your code starting from the implementation details and continuing into higher levels of abstraction.
This is the opposite of what you want. Think about it. When you write a class as a C# developer, you start with the public API at the top of the class definition. Then you write the implementation of each method from top to bottom, from higher to lower levels of abstraction so that reading becomes natural. You open a file at the top, understand what it does at a glance by reading the intentional names that compose its public API and then you traverse the file down looking at the implementation details only when you need or want to.
Being forced to start from the opposite direction will have a negative impact in the readability of your code:
If you try to reorder the different functions within the module so that they start from the public API and continue from top to bottom, from higher to lower levels of abstraction you’ll encounter many issues:
In this example we use function expressions to define every function. Because they are hoisted like variables when we try to assign the enchant function to our magic.enchant object its value is undefined. This results in us exposing an undefined value to the outside world instead of a helpful function to enchant delicious cakes. In a similar way, when we attempt to call the enchant function before either enchant or mix have been initialized we get a TypeError exception.
In summary, both named and anonymous function expressions are hoisted as variables. This affects their readability and can cause bugs when we try to run a function or expose it as part of the public API of a module before it has been defined. Although you could use let and const to prevent hoisting, there’s a better way you can declare your functions: Function declarations.
Function Declarations
Function declarations have some advantages over function expressions:
They are named, and you can use that name from outside and from within the function.
They are not hoisted as variables but as a whole, which makes them impervious to hoisting problems.
This is how you write a function declaration:
As you learned just a moment ago, function declarations are hoisted in a very special way. When the JavaScript runtime processes this piece of code:
It’s going to rearrange it by hoisting the whole sayHello function and only the declaration of the variables message and sayHi:
Because of this special hoisting behavior, function declarations will enable you to write your JavaScript modules from higher to lower levels of abstraction just like we discussed earlier and as you can see in this example below:
And, just like function expressions, you can also use function declarations as values (and arguments). Notice the disintegrate function below:
There’s something interesting happening in this example above that is worthy of note: type coercion. When the body of the disintegrate function is evaluated, the expression below:
Coerces the thing variable to a string type. In the case of the orc and warg which are objects that means calling the toString method and obtaining a string representation of either of these objects.
Concluding: Prefer Function Declarations and Named Function Expressions
Function expressions have some limitations:
They are anonymous, which can make them less readable, harder to debug and use in recursion
They are hoisted as variables which can lead to bugs and forces you to declare them before you can use them
Named function expressions solve the problem of anonymity. They make your vanilla function expressions more readable, easier to debug and enable recursion.
Function declarations solve both problems of anonymity and hoisting (they are hoisted as a whole), and even allow you to write code from higher to lower levels of abstraction.
You can use the let and const keywords to solve the problems with hoisting related to function expressions but you don’t get the nice top to bottom narrative that you get with function declarations. That is, with let and const you cannot use a function before you have declared it, if you attempt to do so you’ll get an exception.
In summary, and based on the characteristics of functions in JavaScript, prefer named function expressions and function declarations over anonymous function expressions.
Hope you have enjoyed this chapter about the different ways you can write functions in JavaScript. Up next, We will discuss the most common patterns to achieve default values, multiple arguments and function overloading when writing functions in JavaScript and we’ll see how some of the new features in ES6 provide native support for some of these.