barbarian meets coding

WebDev, UX & a Pinch of Fantasy

Mastering the Arcane Art of JavaScript-mancy for C# Developers - Chapter 6: Functions in ES2015

| 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.

Welcome back! Today we will make a summary of the different ES2015 features related to functions that we’ve seen and worked with so far in the series: destructuring, default arguments and rest parameters. This will help you to consolidate these new features in your mind and also dive into them a little bit deeper.

We will also take a look at the new arrow function and how it solves the this conumdrum that we discussed in chapter 1 and will end up discussing smaller features like let, const and the spread operator.

ES2015 and functions

ES2015

ES2015 (also known as ES6) is the next version of JavaScript. Although many browsers have started providing support ES2015 the specification itself is still in a draft format and will be hopefully approved by the ECMA standards committee this month of June 2015.

ES2015 and Functions

We are going to take a deeper look at several new features in ES2015 that you’ve seen help us solve limitations that existed in ES5 (the current version of JavaScript) in previous articles of the series. These are: destructuring, default arguments and the rest operator. Additionally, you’ll learn about arrow function expressions which behave much like lambdas in C# providing a terser way to write function expressions and solve the this conumdrum by being automatically bound to the surrounding context.

If you so wish, you can find with all code examples in jsFiddle and experiment along as you read. You’ll need to run the samples in babel.js since it will take some time before your browser supports all these features (you just need to copy the examples and paste them inside their REPL).

Destructuring

ES2015 comes with a very handy new feature called destructuring that lets you extract parts of information from within objects and other data structures. You saw how this could be useful when used within the parameter list of a function signature:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// With destructuring we can unpack the direction from
// the incoming object and use it right away
function castIceCone(mana, {direction}){
    var caster = this || 'God almighty';

    // new template strings
    console.log(`${caster} spends ${mana} mana and casts a terrible ice cone ${direction}`);
}
var jaime = {
    toString: function(){return 'Jaime the Mighty';},
    castIceCone: castIceCone
};
jaime.castIceCone(10, { direction: 'towards Mordor'})
// => Jaime the Mighty spends 10 mana and casts a terrible ice cone towards Mordor

But you can also use destructuring outside of the context of a function to extract parts of objects:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Use destructuring with objects
console.log("==== Destructuring objects =====");
var jaime = {firstName: 'jaime',
             lastName: 'the barbarian',
             height: 178,
             weight: 90,
             toString: function() {
                 return "JAIME";
             }};

// we extract the firstName and lastName properties of the jaime object
let {firstName, lastName} = jaime
console.log(`Destructured ${jaime} into firstName '${firstName}' and lastName '${lastName}'`);
// => Destructured JAIME into firstName 'jaime' and lastName 'the barbarian'

You are not limited to using variables that have exactly the same names than the properties within the original object:

1
2
3
4
5
6
7
// and using different names than that of the original properties
// we can extract the lastName property of the jaime object and
// put it within the title variable
// (you use the source:destination notation)
let {lastName:title} = jaime;
console.log(`jaime's title is ${title}`);
// => jaime's title is the barbarian

You are not limited to using destructuring on objects either, you can even destructure arrays:

1
2
3
4
// you can even use destructuring with arrays
let [one, two, three] = ['globin', 'ghoul', 'ghost', 'white walker'];
console.log(`one is ${one}, two is ${two}, three is ${three}`)
// => one is globin, two is ghoul, three is ghost

Which comes very handy when you want to get the first element of an array in a very readable and intuitive fashion:

1
2
3
4
// this comes very handy if you want to get the first element of an array
let [first, ...rest] = ['goblin', 'ghoul', 'ghost', 'white walker'];
console.log(`first is ${first} and then go all the rest: ${rest}`)
// => first is goblin and then go all the rest ghoul, ghost, white walker

Default Parameters

Like in C# default parameters let you provide default values for function arguments that are not given when a function is called or that are undefined.

You can see how simple it is to start using default parameters:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
    // run on babel.js
    function castIceCone(mana=5){
        // we take advantage of the || operator to define defaults
        var caster = this || 'God almighty';

        // note this new ES2015 template strings btw
        console.log(`${caster} spends ${mana} mana and casts a terrible ice cone`);
    }

    castIceCone();
    // => God almighty spends 5 mana and casts a terrible ice cone

    var jaime = {
        toString: function(){return 'Jaime the Mighty';},
        castIceCone: castIceCone
    };
    jaime.castIceCone(10, { direction: 'towards Mordor'})
    // => Jaime the Mighty spends 10 mana and casts a terrible ice cone

Additionally, defaults in JavaScript are not limited to “constant” expressions like in C# optional arguments. In JavaScript you can use even objects and the destructuring syntax to assign object properties to variables directly.

In the example below we use the object {direction: 'in front of you'} as a default value for the second argument and automatically declare a variable direction and assign the 'in front of you' value to it:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// defaults are not limited to constant values, you can even use objects
function castIceCone(mana=5, {direction='in front of you'}={}){
    var caster = this || 'God almighty';

    // new template strings
    console.log(`${caster} spends ${mana} mana and casts a terrible ice cone ${direction}`);
}

castIceCone();
// => God almighty spends 5 mana and casts a terrible ice cone in front of you

var jaime = {
    toString: function(){return 'Jaime the Mighty';},
    castIceConePro: castIceConePro
};
jaime.castIceConePro(10, { direction: 'towards Mordor'})
// => Jaime the Mighty spends 10 mana and casts a terrible ice cone towards Mordor

And you are not limited to arbitrary objects either, you can use any expression, for instance, a function expression:

1
2
3
4
5
6
7
8
9
// defaults are not limited to constant values
function castSpell(spell=function(){console.log('holy shit a callback!');}){
    spell();
}

 castSpell();
 // => holy shit a callback!
 castSpell(function(){console.log("balefire!!!! You've been wiped out of existence");});
 // => balefire!!!! You've been wiped out of existence

Another interesting feature of default parameters is that you can use them even when destructuring objects or arrays outside of the context of a function:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// you can even use defaults when destructuring objects or arrays
var jaime = {firstName: 'jaime',
             lastName: 'the barbarian',
             height: 178,
             weight: 90,
             toString: function() {
                 return "JAIME";
             }};

let {firstName, lastName, rank='noob'} = jaime
console.log(`Destructured ${jaime} into firstName '${firstName}', lastName '${lastName}' and rank '${rank}'`);
// => Destructured JAIME into firstName 'jaime', lastName 'the barbarian' and rank 'noob'

let [one, two, three='cucumber'] = ['globin', 'ghoul'];
console.log(`one is ${one}, two is ${two}, three is ${three}`)
// => one is globin, two is ghoul, three is cucumber

The Rest Operator

In Chapter 3 of the series you saw how you can use rest parameters to achieve something similar to C# params keyword and handle multiple parameters within a function in a seamless fashion:

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
// Taking a variable number of parameters in JavaScript in ES2015 is easy
function obliterate(...victims){
    victims.forEach(function(victim){
        console.log(victim + " wiped out of the face of the earth");
    });
    console.log('*Everything* has been obliterated, oh great master of evil and deceit!');
}

obliterate("John Doe", getPuppy(), 1, new Minion('Willy', 'troll'));
/*
=> John Doe wiped out of the face of the earth
=> cute puppy wiped out of the face of the earth
=> 1 wiped out of the face of the earth
=> Willy the troll wiped out of the face of the earth
=> *Everything* has been obliterated, oh great master of evil and deceit!
*/

function getPuppy(){
    return {
        cuteWoof: function(){console.log('wiii');},
        toString: function(){return 'cute puppy';}
    };
};

function Minion(name, type){
    this.name = name;
    this.type = type;
    this.toString = function(){ return name + " the " + type;};
}

An Introduction to The Arrow Function

Arrow functions are a very nice new feature in ES2015 indeed:

  • they give you a terser syntax than a traditional function expression and,
  • they are automatically bound to their context, that is, this takes the value of a function’s surrounding context. Bound not in the sense of bind function but in the sense of that arrow functions don’t have their own version of this. Because of this when you use this within an arrow function you access the this of the enclosing environment.

Because arrow functions are automatically bound to their context, they solve the problem with callbacks and this that we saw in the chapter 1 of the series.

Let’s illustrate the value proposition of the arrow function with the same example we used in chapter 1:

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
// Another example with jQuery
// This is the original example
// that showcased the problem with 'this' and callbacks
console.log("===== this is what happens when trying to use `this` within a callback ====");
function UsersCatalogJQuery(){
    "use strict";
    var self = this;

    this.users = [];
    getUsers()

    function getUsers(){
        $.getJSON('https://api.github.com/users')
        .success(function(users){
            // console.log(users);
            // console.log(this);
            try {
              this.users.push(users);
            } catch(e) {
              console.log(e.message);
            }
            // BOOOOOOOM!!!!!
            // => Uncaught TypeError: Cannot read property 'push' of undefined
            // 'this' in this context is the jqXHR object
            // not our original object
            // that's why we usually use a closure here instead:
            // self.products = products;
        });
    }
}
var catalog = new UsersCatalogJQuery();

If you substitute the anonymous function expression for an arrow function, you solve the problem! And in a much elegant way than binding the function explicitly (with bind) or by using a closure (self):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
console.log("===== but we can fix it with the arrow function! ====");
function UsersCatalogJQueryArrowFunction(){
    "use strict";
    this.users = [];
    this.getUsers = function getUsers(){
        $.getJSON('https://api.github.com/users')
        .success(users => this.users.push(users)); // hello arrow function
        // this is equivalent to:
        // .success(function(users){return this.users.push(users);}.bind(this))
    };

    this.getUsers();
}
var catalog = new UsersCatalogJQueryArrowFunction();

Like in C#, you can define arrow functions with any number of arguments:

1
2
3
let helloMiddleEart = () => "hello Middle Earth!";
let destroyDaRing = (hobbit) => hobbit.destroyTheOneRing();
let useElvenCloak = (hobbit, nazgul) => hobbit.hideFrom(nazgul);

Other Small Goodies in ES2015

Block Scope with Let

The let keyword gives your the ability to declare variables with block scope and attempts to solve the issues caused by JavaScript variables having function scope that you saw in Chapter 1 of theaseries. From now on you can use either var for function scope or let for block scope, pick the one you’re happy with. If you are a C# developer and are accustomed to block scope then let will probably be your weapon of choice.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// let lets you declare variables in block scope
// as opposed to var that uses function scope
console.log("======= Function Scope ========");
function aFunctiontoIllustrateFunctionScope(){

    if (true){
        var x = 10;
    }
    console.log(x);
}
aFunctiontoIllustrateFunctionScope();
// => 10 WAT

console.log("======= Block Scope ========");
function aFunctiontoIllustrateBlockScope(){

    if (true){
        let x = 10;
    }
    console.log(x);
}
aFunctiontoIllustrateBlockScope();
// => referenceError: x is not defined

The Spread Operator

The spread operator works sort of in an opposite way to the rest operator. Where the rest operator takes a variable number of arguments and packs them into an array, the spread operator takes and array and expands it into separate items. Let’s see this through a couple of examples:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// lets you expand arrays into positional arguments
var newFoes = ['globin', 'ghoul'];
var knownFoesLevel1 = ['rat', 'rabbit']
var knownFoesLevel2 = ['rat', 'rabbit', ...newFoes];

console.log(knownFoesLevel1); // => rat, rabbit
console.log(knownFoesLevel2); // => rat, rabbit, goblin, ghoul

// you can also use it when calling a function
var action = ['hobbit', 'attacks', 'rabbit'];
var anotherAction = ['jaime', 'cleans', 'the dishes'];
function performActionViciously(agent1, action, agent2){
    console.log(`${agent1} ${action} ${agent2} viciously`);
}

performActionViciously(...action); // => hobbit attacks rabbit viciously
performActionViciously(...anotherAction); // => jaime cleans the dishes viciously

Immutable Variables with Const

With ES2015 (ES6) const joins let and var and allows you to declare constants (immutable “variables”). It works in a similar fashion to C# const keyword and it will protect a variable from being changed.

1
2
3
4
const death = "death";
const time = "time";
death = "sandwich";
// => SyntaxError: "death" death is read-only

If however a variable contains a reference type like an object or an array, it won’t prevent anyone from changing their values/contents.

1
2
3
4
5
6
// but if they refer to reference types
// although you cannot change the constant itself
// you can change the object/array it references
const fourHorsemen = ['conquest', 'war', 'famine', 'death'];
fourHorsemen.push('jaime');
console.log(`${fourHorsemen} waaat`);

To achieve true object immutability you can use ES5 Object.freeze method that prevents properties from being added to an object, from being modified, and from being configured.

1
2
3
4
// you can create a true immutable object with Object.freeze
const fourHorsemen = Object.freeze(['conquest', 'war', 'famine', 'death']);
fourHorsemen.push('jaime');
// => TypeError: cannot assign to read only property...

If you are interested in learning more about the very many Object functions that came with ES5 you can take a look at Nicholas C. Zakas’ great book on JavaScript OOP.

Concluding

In this article you saw a summary of the great features that are coming to JavaScript with ES2015 (ES6) related to functions and that we’ve previously seen in other articles within the series: destructuring, default arguments and rest parameters. We also took a look at the arrow function that promises to solve a lot of headaches with the this keyword while bringing the nice, terse syntax of lambdas and other smaller features like let, const and the spread operator.

Next in the series, we will take a look at how you can use “LINQ” in JavaScript. See you soon!

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

References

These are some awesome references that you can take a look at if you want to find out more about ES2015 (ES6):

More Articles in These Series

Comments