The Basics Of JavaScript Functions
functions,
functions everywhere
- Buzz Lightyearvascript
Explorer
The Basics Of Functions
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:
1 // anonymous function expression
2 var castFireballSpell = function(){
3 // chaos and mayhem
4 };
And this is a function declaration:
1 // function declaration
2 function castFireballSpell(){
3 // it's getting hot in here...
4 }
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:
1 // an anonymous function expression
2 var castFireballSpell = function(){
3 console.log('...chaos and mayhem');
4 };
5 castFireballSpell();
6 // => ...chaos and mayhem
A property within an object:
1 // another anonymous function expression as a property of an object
2 var magician = {
3 castFireballSpell: function() {
4 console.log('muahaha! Eat this fireball!');
5 }
6 };
7 magician.castFireballSpell();
8 // => muahaha! Eat this fireball!
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):
1 // yet another anonymous function expression passed as an argument
2 var castCombo = function(spellOne, spellTwo){
3 console.log('Combo Magic!!!!');
4 spellOne();
5 spellTwo();
6 }
7
8 castCombo(function(){
9 console.log('FireBalllllzzz!!');
10 }, function(){
11 console.log('And Spectral Shield!!');
12 });
13
14 // => Combo Magic!!!!
15 // => FireBalllllzzz!!
16 // => And Spectral Shield!!
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?.
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:
1 function openPandoraBox(){
2
3 if (true){
4 var treasure = 'mouse';
5 }
6 console.log('the pandora box holds a: **' + treasure + '**');
7 }
8 openPandoraBox();
9 // => the pandora box holds a: **mouse**
10 // WAT!? x is declared inside an if block.
11 // How can it be picked up by the console.log??
12 // one word: function-scope
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:
1 function openPandoraBox(){
2 // The variable definition is hoisted up here
3 var treasure = undefined;
4 if (true){
5 treasure = 'mouse';
6 }
7 console.log('the pandora box holds a: **' + treasure + '**');
8 // => the pandora box holds a: **mouse**
9 }
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!!):
1 function aMagicFunctionToIllustrateHoisting(){
2 // in reality
3 // var i = undefined
4
5 console.log('before i =' + i);
6 // => before i = undefined
7
8 for(var i = 0; i < 10; i++){
9 // jara jara
10 }
11
12 console.log('after i = ' + i);
13 // => after i = 10
14 }
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:
1 /**
2 * Load a url into a page
3 */
4 jQuery.fn.load = function( url, params, callback ) {
5 var selector, type, response,
6 self = this,
7 off = url.indexOf(" ");
8
9 //... more codes here
10 }
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:
1 // declaring a function inside a function?
2 // Ain't that weird????
3 function blessMany(many){
4 many.forEach(bless);
5
6 function bless(target){
7 console.log('You bless ' + target + '. (+5 WillPower) ');
8 }
9 }
10
11 blessMany(['john snow', 'sansa stark']);
12 // => You bless John Snow (+5 Willpower)
13 // => You bless Sansa Stark (+5 Willpower)
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:
1 function openPandoraBoxWithBlockScope(){ // new scope for function b\
2 lock
3
4 if (true){ // new scope for if block
5 let treasure = 'mouse';
6 }
7 console.log('the pandora box holds a: **' + treasure + '**');
8 }
9 openPandoraBoxWithBlockScope();
10 // ReferenceError: treasure is not defined
11 // fiuuuu now everything makes sense again
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.
1 function shallIPass(){ // new scope for youShallNotPass block
2 let youShallPass = 'you Shall Pass!',
3 youShallNotPass = 'You Shall Not Pass!';
4 // console.log(canIPass); // => ReferenceError
5
6 if (true){ // new scope for if block
7 const canIPass = youShallNotPass;
8 console.log(canIPass); // => 'You Shall Not Pass!'
9 canIPass = youShallPass;
10 // => TypeError: Assignment to a constant variable
11 }
12
13 console.log(canIPass); // => undefined
14 // ReferenceError: x is not defined
15 }
16 shallIPass();
17 // => you Shall not Pass!
18 // => TypeError: Assignment to a constant variable
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:
1 const fourHorsemen = ['conquest', 'war', 'famine', 'death'];
2 fourHorsemen.push('jaime');
3 console.log(`${fourHorsemen} waaat`);
4 // => ['conquest', 'wat', 'famine', 'death', 'jaime'] waaat
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):
1 function openPandoraBoxWithBlockScopeAndHoisting(){
2 // new scope for function block
3
4 if (true){
5 // new scope for if block
6 console.log('the pandora box holds a: **' + treasure + '**');
7 // => ReferenceError: treasure is not defined;
8 let treasure = 'mouse';
9 }
10 }
11 openPandoraBoxWithBlockScopeAndHoisting();
12 // => ReferenceError: treasure is not defined;
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:
1 var castFireballSpell = function(){
2 console.log('...chaos and mayhem');
3 };
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:
1 var castFireballSpell = function(){
2 console.log('...chaos and mayhem');
3 };
4 console.log(castFireballSpell.name);
5 // => ""
6 // no name!
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):
1 // inspecting a call stack for an anonymous function bound
2 // to a variable
3 var castFireballSpellWithError = function(){
4 console.log('...chaos and mayhem');
5 try {
6 throw new Error();
7 } catch (e) {
8 console.log('stack: ', e.stack);
9 }
10 };
11 castFireballSpellWithError();
12 //=> stack: Error
13 // at castFireballSpellWithError (somefile:53:15)
14 // at window.onload (somefile:58:1)
Even if we use this function as a argument:
1 // If you use this function as a lambda the name is
2 // still shown in the call stack:
3 var spellLauncher = function(f){ f(); }
4 spellLauncher(castFireballSpellWithError);
5 // => stack: Error
6 // at castFireballSpellWithError (somefile:56:15)
7 // at spellLauncher (somefile:68:35)
8 // at window.onload (somefile:69:1)
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):
1 // strict anonymous function don't appear in the call stack
2 spellLauncher(function(){
3 console.log('...chaos and mayhem');
4 try {
5 throw new Error();
6 } catch (e) {
7 console.log('stack: ', e.stack);
8 }
9 });
10 //=> stack: Error
11 // at somefile:76:15
12 // at spellLauncher (somefile:68:35)
13 // at window.onload (somefile:73:1)
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:
1 // you can use recursion when an anonymous function is bound to a va\
2 riable
3 var castManyFireballsSpell = function(n){
4 // this function encloses the castManyFireballsSpell variable
5 // and thus becomes a closure
6 console.log('... SHOOOOOOOOOSH ....');
7 if (n > 0)
8 castManyFireballsSpell(n-1);
9 };
10 castManyFireballsSpell(3);
11 // => ... SHOOOOOOOOOSH ....
12 // ... SHOOOOOOOOOSH ....
13 // ... SHOOOOOOOOOSH ....
14 // ... SHOOOOOOOOOSH ....
Notice that this is a pretty brittle way of using 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:
1 // but there's no way for an anonymous function
2 // to call itself in any other way
3 (function(n){
4 console.log('... SHOOOOOOOOOSH ....');
5 if (n > 0) {
6 // I cannot call myself... :(
7 }
8 }(5));
9 // => ... SHOOOOOOOOOSH ....
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 example program.
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:
1 // named function expression
2 var castFireballSpell = function castFireballSpell(){
3 // mayhem and chaos
4 };
5 console.log("this function's name is: ", castFireballSpell.name);
6 // => this function's name is castFireballSpell
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):
1 // A named function expression always appears in the call stack
2 spellLauncher(function spreadChaosAndMayhem(){
3 console.log('...chaos and mayhem');
4 try {
5 throw new Error();
6 } catch (e) {
7 console.log('stack: ', e.stack);
8 }
9 });
10 // stack: Error
11 // at spreadChaosAndMayhem (somefile:134:15)
12 // at spellLauncher (somefile:68:35)
13 // at window.onload (somefile:131:1)
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:
1 var castFireballSpell = function cucumber(){
2 // cucumber?
3 };
4 cucumber();
5 // => ReferenceError: cucumber is not defined
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:
1 // but you can call it from the function body
2 // which is helpful for recursion
3 (function fireballSpellWithRecursion(n){
4 console.log('... SHOOOOOOOOOSH ....');
5 if (n > 0) {
6 fireballSpellWithRecursion(n-1);
7 }
8 }(5));
9 // => ... SHOOOOOOOOOSH ....
10 // ... SHOOOOOOOOOSH ....
11 // ... SHOOOOOOOOOSH ....
12 // ... SHOOOOOOOOOSH ..... etc..
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:
1 (function (magic){
2 // this function represents a module 'magic'
3 // it's just a way to group like-minded pieces of code
4
5 var oven = {
6 open: function(){},
7 placeBaking: function(){},
8 increaseTemperature: function(){},
9 claimVictory: function(){ return 'awesome cake';}
10 };
11
12 var mix = function mix(ingredients){
13 console.log('mixin ingredients:', ingredients.join(''));
14 return 'un-appetizing mixture';
15 }
16
17 var work = function work(mixture){
18 console.log('working ' + mixture);
19 return 'yet more un-appetizing dough';
20 };
21
22 var bake = function bake(dough){
23 oven.open();
24 oven.placeBaking(dough);
25 oven.increaseTemperature(200);
26 // insta-oven!
27 return oven.claimVictory();
28 };
29
30 var enchant = function enchant(ingredients){
31 var mixture = mix(ingredients),
32 dough = work(mixture),
33 cake = bake(dough);
34 return cake;
35 };
36
37 // This is the public API of this module
38 // and it's almost hidden down here
39 magic.enchant = enchant;
40
41 }(window.magic || (window.magic = {})));
42
43 var cake = magic.enchant(['flour', 'mandragora', 'dragon', 'chicken \
44 foot']);
45 // => mixin ingredients: flour mandragora dragon chicken foot
46 // => working un-appetizing mixture
47 console.log(cake);
48 // => awesome cake
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:
1 (function (magic){
2 // this function represents a module 'magic'
3 // it's just a way to group like-minded pieces of code
4
5 // exposing enchant as the API for the 'magic' module
6 magic.enchant = enchant;
7 // => hoisting issue, enchant is undefined at this point
8 // so we are just exposing an undefined variable thinking it is a \
9 function
10
11 // if uncommented this would cause an exception
12 // enchant();
13 // => TypeError: enchant is not a function
14 // => hoisting issue, enchant is undefined at this point
15
16 var enchant = function enchant(ingredients){
17 var mixture = mix(ingredients),
18 dough = work(mixture),
19 cake = bake(dough);
20 return cake;
21 };
22
23 // if uncommented this would cause an exception
24 // enchant();
25 // => TypeError: mix is not a function (it's undefined at this poi\
26 nt)
27 // hoisting issue, mix is undefined at this point
28
29 /* rest of the code...
30 var mix = function mix(ingredients){}
31 var work = function work(mixture){};
32 var bake = function bake(dough){};
33 var oven = {};
34 */
35
36 }(window.magic || (window.magic = {})));
37
38 try {
39 var cake = magic.enchant(['flour', 'mandragora', 'dragon', 'chicke\
40 n foot']);
41 console.log(cake);
42 } catch (e) {
43 console.warn('ups!!!!!!', e);
44 // => ups!!!!!! TypeError: magic.enchant is not a function
45 }
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:
1 // function declaration
2 function castFireballSpell(){
3 // it's getting hot in here...
4 }
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:
1 var message = "hello";
2
3 // some more code here...
4
5 // function expression
6 var sayHi = function(){console.log('hi!')};
7
8 // some more code here...
9
10 // function declaration
11 function sayHello(){
12 console.log(msg);
13 }
It’s going to rearrange it by hoisting the whole sayHello function and only the declaration of the variables message and sayHi:
1 // hoisted as variables
2 var message, sayHi;
3
4 // hoisted as a whole
5 function sayHello(){
6 console.log(msg);
7 }
8
9 message = "hello";
10 // some more code here...
11 sayHi = function(){console.log('hi!')};
12 // some more code here...
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:
1 // with function declarations you can write functions like this
2 // and don't worry about hoisting issues at all
3 (function(magic){
4
5 // public API of the magic module
6 magic.enchant = enchant;
7
8 // functions from higher to lower level of abstraction
9 function enchant(ingredients){
10 var mixture = mix(ingredients),
11 dough = work(mixture),
12 cake = bake(dough);
13 return cake;
14 }
15
16 // these are private functions to this module
17 function mix(ingredients){
18 console.log('mixin ingredients:', ingredients.join(''));
19 return 'un-appetizing mixture';
20 }
21
22 function work(mixture){
23 console.log('working ' + mixture);
24 return 'yet more un-appetizing dough';
25 }
26
27 function bake(dough){
28 oven.open();
29 oven.placeBaking(dough);
30 oven.increaseTemperature(200);
31 // insta-oven!
32 return oven.claimVictory();
33 }
34
35 var oven = {
36 open: function(){},
37 placeBaking: function(){},
38 increaseTemperature: function(){},
39 claimVictory: function(){ return 'awesome cake';}
40 };
41
42 }(window.magic || (window.magic = {})));
43
44 var cake = magic.enchant(['flour', 'mandragora', 'dragon', 'chicken \
45 foot']);
46 // => mixin ingredients: flour mandragora dragon chicken foot
47 // => working un-appetizing mixture
48 console.log(cake);
49 // => awesome cake
And, just like function expressions, you can also use function declarations as values (and arguments). Notice the disintegrate function below:
1 var orc = {toString: function(){return 'a mighty evil orc';}};
2 var warg = {toString: function(){return 'a terrible warg';}};
3 var things = [1, orc, warg, false];
4
5 // using the disintegrate function declaration as an argument
6 // nice and readable
7 things.forEach(disintegrate);
8
9 function disintegrate(thing){
10 console.log(thing + ' suddenly disappears into nothingness...');
11 }
12
13 // => 1 suddenly disappears into nothingness...
14 // a mighty evil orc suddenly disappears into nothingness...
15 // a terrible warg suddenly disappears into nothingness...
16 // false suddenly disappears into nothingness...
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:
1 thing + ' suddenly disappears into nothingness...'
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.