barbarian meets coding

WebDev, UX & a Pinch of Fantasy

Mastering the Arcane Art of JavaScript-Mancy for C# Developers - Chapter 2: The Basics of JavaScript Functions

| Comments

The Mastering the Arcane Art of JavaScript-mancy series are my humble attempt at bringing my love for JavaScript to all other C# developers that haven’t yet discovered how awesome this language and its whole ecosystem are. These articles are excerpts of the super duper awesome JavaScript-Mancy book a compendium of all things JavaScript for C# developers.

Functions are the most foundational building block in JavaScript. Not only do they hold the logic of our applications, they also are the primitives upon which we build other programmatic constructs in JavaScript like classes and modules.

JavaScript provides different ways to declare and use functions, each with their own nuances and limitations, so given 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 applications.

Welcome to another step in your journey to JavaScript mastery! Let’s get started!

JavaScript-Mancy

The Basics of Functions

You can experiment with all examples in this blog post within this jsFiddle.

There are two ways to write functions in JavaScript: function expressions and function declarations. It is important to know the implications of using either of these two styles since they work in a very different manner that will impact not only the readability of your code but also how easy/hard it is to debug.

This is a function expression:

1
2
3
4
// anonymous function expression
var castFireballSpell = function(){
  // chaos and mayhem
};

And this is a function declaration:

1
2
3
4
// function declaration
function castFireballSpell(){
  // it's getting hot in here...
}

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 do are different, have different characteristics and behave in different ways from each other.

Let’s examine them in greater detail.

Function Expressions

Function expressions result 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):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// an anonymous function expression
var castFireballSpell = function(){
    console.log('...chaos and mayhem');
};

// another anonymous function expression as a property of an object
var magician = {
    castFireballSpell: function() {
        console.log('muahaha! Eat this fireball!');
    }
};

// yet another anonymous function expression passed as an argument
var castFireballSpell = function(){
    console.log('...chaos and mayhem');
};

There are a couple of important considerations you need to take into account when using function expressions like the ones described above: they are all anonymous functions, and if stored within a variable they are subjected to the same hoisting rules that apply to any other variable.

Anonymous Function Expressions

So even though you read:

1
2
3
var castFireballSpell = function(){
    console.log('...chaos and mayhem');
};

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
2
3
4
5
6
var castFireballSpell = function(){
    console.log('...chaos and mayhem');
};
console.log(castFireballSpell.name);
// => ""
// no name!

Luckily for us, for as long as an anonymous function is bound to a variable the developer tools will use that variable when displaying errors in call stacks (good when debugging):

1
2
3
4
5
6
7
8
9
10
11
12
13
console.log("you can however examine the call stack and see how an anonymous function that is bound to a variable shows the variable name");
var castFireballSpellWithError = function(){
    console.log('...chaos and mayhem');
    try {
        throw new Error();
    } catch (e) {
        console.log('stack: ', e.stack);
    }
};
castFireballSpellWithError();
//=> stack:  Error
//     at castFireballSpellWithError (http://fiddle.jshell.net/_display/:53:15)
//     at window.onload (http://fiddle.jshell.net/_display/:58:1)

Even if we use this function as a lambda:

1
2
3
4
5
6
7
console.log("if you use this function as a lambda the name is still shown in the call stack");
var functionCaller = function(f){ f(); }
functionCaller(castFireballSpellWithError);
// => stack:  Error
//    at castFireballSpellWithError (http://fiddle.jshell.net/_display/:56:15)
//    at functionCaller (http://fiddle.jshell.net/_display/:68:35)
//    at window.onload (http://fiddle.jshell.net/_display/:69:1)

However, an anonymous function not bound to a variable will show as completely anonymous within the call stack making it harder to debug possible errors (I will call these functions as strict anonymous function in the remainder of the article):

1
2
3
4
5
6
7
8
9
10
11
12
13
console.log("it is only if you use a strict anonymous function that the name doesn't appear in the call stack");
functionCaller(function(){
    console.log('...chaos and mayhem');
    try {
        throw new Error();
    } catch (e) {
        console.log('stack: ', e.stack);
    }
});
//=> stack:  Error
//    at http://fiddle.jshell.net/_display/:76:15
//    at functionCaller (http://fiddle.jshell.net/_display/:68:35)
//    at window.onload (http://fiddle.jshell.net/_display/:73:1)

This lack of name can also affect the ability to use recursion. Again, as long as we have the anonymous function bound to a variable we can use the closure to access the function and use recursion:

1
2
3
4
5
6
7
8
9
10
11
console.log("you can use recursion when an anonymous function is bound to a variable");
var castManyFireballsSpell = function(n){
    console.log('... SHOOOOOOOOOSH ....');
    if (n > 0)
        castManyFireballsSpell(n-1);
};
castManyFireballsSpell(3);
// => ... SHOOOOOOOOOSH ....
//    ... SHOOOOOOOOOSH ....
//    ... SHOOOOOOOOOSH ....
//    ... SHOOOOOOOOOSH ....

It is important to notice that in this example you are just using a closure to access a variable from within a function, and if you, at some point in time, set the variable castManyFireballsSpell to another function, you would get a subtle bug where the original function would call this new function instead of itself (so no recursion and weird stuff happening).

A strict anonymous function, on the other hand, has no way to refer to itself and thus you cannot use recursion:

1
2
3
4
5
6
7
8
console.log("but there's no way for an anonymous function to call itself in any other way");
(function(n){
    console.log('... SHOOOOOOOOOSH ....');
    if (n > 0) {
        // I cannot call myself... :(
    }
}(5));
// => ... SHOOOOOOOOOSH ....

So, in summary, the fact that common function expressions are anonymous affects negatively to their readability, debugging and the user of recursion. Let’s see some ways to ameliorate 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
2
3
4
5
6
// named function expression
var castFireballSpell = function castFireballSpell(){
  // mayhem and chaos
};
console.log("this function's name is: ", castFireballSpell.name);
// => this function's name is castFireballSpell

A named function expression always appears correctly represented in the call stacks even when used as a lambda (and not bound to a variable):

1
2
3
4
5
6
7
8
9
10
11
12
13
console.log("A named function expression always appears in the call stack, even when used as a lambda not bound to a variable");
functionCaller(function spreadChaosAndMayhem(){
    console.log('...chaos and mayhem');
    try {
        throw new Error();
    } catch (e) {
        console.log('stack: ', e.stack);
    }
});
// stack:  Error
//     at spreadChaosAndMayhem (http://fiddle.jshell.net/_display/:134:15)
//     at functionCaller (http://fiddle.jshell.net/_display/:68:35)
//     at window.onload (http://fiddle.jshell.net/_display/:131:1)

This helps both while debugging and makes the code more readable – you can read the name of the function and understand what it attempts to do without looking at the specific implementation.

It is important to understand that the name of the function expression is only used internally, that is, you cannot call a function expression by its name from the outside:

1
2
3
4
5
var castFireballSpell = function cucumber(){
    // cucumber?
};
cucumber();
// => ReferenceError: cucumber is not defined

But you can do it from the function body, which is very useful when working with recursion:

1
2
3
4
5
6
7
8
9
10
11
console.log('but you can call it from the function body which is helpful for recursioon');
(function fireballSpellWithRecursion(n){
    console.log('... SHOOOOOOOOOSH ....');
    if (n > 0) {
        fireballSpellWithRecursion(n-1);
    }
}(5));
// => ... SHOOOOOOOOOSH ....
//    ... SHOOOOOOOOOSH ....
//    ... SHOOOOOOOOOSH ....
//    ... SHOOOOOOOOOSH ..... etc..

In summary, named function expressions improve on anonymous function expressions increasing readability, improving the debugging process and allowing for a function to call itself.

Function Expressions are Hoisted as Variables

We still have a problem with hoisting though: Function expressions are hoisted like variables which means that you cannot use a function expression until after you have declared it. This affects readability (since you have to read your code from bottom to top instead of from top to bottom) and can lead to unexpected bugs.

The following example illustrates some of these problems and is also available in jsFiddle:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
// if we recap hoisting of variables in JS quickly
// this example:
(function(magic){
    magic.enchant2 = enchant;
    var enchant = function(){ // something };
}(window.magic || (window.magic = {})))
// is equivalent to:
(function(magic){
    var enchant = undefined;
    magic.enchant2 = enchant;
    enchant = function(){ // something };
}(window.magic || (window.magic = {})))

// you cannot do this with function expressions
(function (magic){
    // illustrate how named function expressions help find errors

    magic.enchant2 = enchant;
    // => hoisting issue, enchant is undefined
    // so we are just exposing an undefined variable thinking it is a function

    // enchant();
    // => TypeError: enchant is not a function
    // => hoisting issue, enchant is undefined


    var enchant = function enchant(ingredients){
        var mixture = mix(ingredients),
            dough = work(mixture),
            cake = bake(dough);
        return cake;
    };

    // enchant();
    // => TypeError: mix is not a function
    // hoisting issue, mix is undefined, and so on...


    var mix = function mix(ingredients){
        console.log('mixin ingredients:', ingredients.join(''));
        return 'un-appetizing mixture';
    }

    var work = function work(mixture){
        console.log('working ' + mixture);
        return 'yet more un-appetizing dough';
    };

    var bake = function bake(dough){
        oven.open();
        oven.placeBaking(dough);
        oven.increaseTemperature(200);
        // insta-oven!
        return oven.claimVictory();
    };

    var oven = {
        open: function(){},
        placeBaking: function(){},
        increaseTemperature: function(){},
        claimVictory: function(){ return 'awesome cake';}
    };

}(window.magic || (window.magic = {})));

try{
    var cake = magic.enchant2(['flour', 'mandragora', 'dragon', 'chicken foot']);
    console.log(cake);
}catch (e){
    console.warn('ups!!!!!!', e);
    // => ups!!!!!! TypeError: magic.enchant2 is not a function
}

Recap! Both named and anonymous function expressions are hoisted as variables which affects readability and can cause bugs when we try to run a function or expose a function as part of the public API of a module before it has been defined.

Function Declarations

And finally we get to function declarations which have some advantages over function expressions:

  • They are named, and you can use that name from the outside and the body of the function itself.
  • They are hoisted not as variables but completely together with their definition, this makes them impervious to hoisting problems.

You write function declarations like this:

1
2
3
4
// function declaration
function castFireballSpell(){
  // it's getting hot in here...
}

And they will enable you to write code more similar to the code you are accustomed to write as a C# developer when writing a class, where you declare the public API of a class at the top of the class definition and then you write the implementation of each method from top to bottom, from higher to lower levels of abstraction:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// with function declarations you can write functions like this
// and not worry about hoisting issues
(function(magic){

    // public API of the magic module
    magic.enchant = enchant;

    // functions from higher to lower level of abstraction
    function enchant(ingredients){
        var mixture = mix(ingredients),
            dough = work(mixture),
            cake = bake(dough);
        return cake;
    }

    // these are private functions to this module
    function mix(ingredients){
        console.log('mixin ingredients:', ingredients.join(''));
        return 'un-appetizing mixture';
    }

    function work(mixture){
        console.log('working ' + mixture);
        return 'yet more un-appetizing dough';
    }

    function bake(dough){
        oven.open();
        oven.placeBaking(dough);
        oven.increaseTemperature(200);
        // insta-oven!
        return oven.claimVictory();
    }

    var oven = {
        open: function(){},
        placeBaking: function(){},
        increaseTemperature: function(){},
        claimVictory: function(){ return 'awesome cake';}
    };

}(window.magic || (window.magic = {})));

var cake = magic.enchant(['flour', 'mandragora', 'dragon', 'chicken foot']);
// => mixin ingredients:  flour mandragora dragon chicken foot
// => working un-appetizing mixture
console.log(cake);
// => awesome cake

You can also use function declarations as lambdas (function as value):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var orc = {toString: function(){return 'a mighty evil orc';}};
var warg = {toString: function(){return 'a terrible warg';}};
var things = [1, orc, warg, false];

// using the disintegrate function declaration as a lambda
// nice and readable
things.forEach(disintegrate);

function disintegrate(thing){
    console.log(thing + ' suddenly disappears into nothingness...');
}

// => 1 suddenly disappears into nothingness...
// a mighty evil orc suddenly disappears into nothingness...
// a terrible warg suddenly disappears into nothingness...
// false suddenly disappears into nothingness...

Also notice how the following example, although it can look as a function declaration, is actually a named function expression:

1
2
3
things.forEach(function disintegrate(thing){
    console.log(thing + ' suddenly disappears into nothingness...');
});

Concluding: Prefer Function Declarations and Named Function Expressions

Time to recap. Function expressions have some limitations:

  1. they are anonymous, which can make them less readable, harder to debug and use in recursion, and
  2. 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 function expressions more readable, easier to debug and enable recursion.

Function declarations solve both the problem of anonymity and hoisting (since they are completely hoisted), and allow you to write code from higher to lower levels of abstraction.

In summary, and based in these characteristics of functions in JavaScript, prefer named function expressions and function declarations over anonymous function expressions.

Coming Next

Hope you have enjoyed this article about the different ways you can write functions in JavaScript. Up next, I will discuss the most common patterns to achieve default values, multiple arguments, function overloading when writing JavaScript functions and I’ll show you how some of the new features in ES 2015 provide native support for some of these.

Interested in Learning More JavaScript? Buy the Book!

Are you a C# Developer interested in learning JavaScript? Then take a look at the JavaScript-mancy book, a complete compendium of JavaScript for C# developers.

a JavaScriptmancy sample cover

Have a great day!

More Articles in These Series

Comments