The WordPress Developer’s Guide to ES2015 (ES6)

#
By Ashley Rich

The WordPress developer community is currently abuzz with JavaScript talk, specifically around which JavaScript framework to use in WordPress core. Regardless of which framework is chosen, it’s clear that WordPress is pushing towards a more modern JavaScript centric approach to UI development. The Customizer, Calypso and Gutenberg are all examples of such UI developments.

If you remember, back in 2015 Matt said to “learn JavaScript deeply,” but, in all honesty, I didn’t take much notice. However, recently I decided to make a concerted effort to brush up on my JavaScript knowledge. In this article I would like to share my favorite parts of ES2015, which will add a touch of elegance to your JavaScript.

What’s ES2015? What About ES6?

I have to admit that until recently I didn’t fully understand exactly what ES2015 was. I knew it was a ‘newer’ version of JavaScript, but I had also seen references to ES6 and Harmony. I presumed they were all different versions, but they are all in fact pseudonyms for the 2015 version of JavaScript. Harmony was the original name, and after that ES6 (because it’s the sixth major version of ECMAScript). It’s since been renamed to ES2015 by ECMA International. Future versions of JavaScript will use the year in their name.

With that out of the way, here are my favorites parts of ES2015:

Improved Objects

Objects have some great additions in ES2015:

Method Shorthand

You may be accustomed to declaring object methods using a key value pair, where value is an anonymous function:

var utils = {
    increment: function( count ) {
        return count + 1;
    }
};

You can now omit the function keyword entirely:

var utils = {
    increment( count ) {
        return count + 1;
    }
};

Much cleaner!

Destructuring

When a function or method returns an object you don’t always require all of the object values. Consider an AJAX request that returns information about the request in addition to any data the server may have returned:

var response = {
    data: {},
    headers: {},
    responseCode: 200,
};

You can of course access the properties using dot notation:

var data = response.data;
var responseCode = response.responseCode;

console.log( data, responseCode ); // {}, 200

However, with object destructuring you don’t need to manually assign each value to a variable:

var { data, responseCode } = response;

console.log( data, responseCode ); // {}, 200

This can simplify your functions or methods because you can unpack the values you require upfront:

function handleResponse( { data, responseCode } ) {
    console.log( data, responseCode );
}

Instead of:

function handleResponse( response ) {
    console.log( response.data, response.responseCode );
}

Classes

Classes have always been somewhat cumbersome in JavaScript. They can also be difficult to grasp, especially for beginner users who are accustomed to PHP classes. This is because class methods in JavaScript are attached to the object’s prototype. Let’s take a simple Form class:

// form.js
function Form( inputs ) {
    this.inputs = inputs;
} // constructor

Form.prototype.submit = function( url ) {
    // Ajax post to server
} // submit() method

ES2015 classes are more akin to PHP classes:

class Form {
    constructor( inputs ) {
        this.input = inputs;
    }

    submit() {
        // Ajax post to server
    }
}

You can also define static methods. So instead of:

Form.inputHelper = function() {
    // Static method
}

You can do:

class Form {
    constructor( inputs ) {
        this.input = inputs;
    }

    submit() {
        // Ajax post to server
    }

    static inputHelper() {
        // Static method
    }
}

Classes can be extended and you can access a parent’s methods using the super keyword:

class OrderForm extends Form {
    submit() {
        super.submit();

        // Do something after ajax post to server
    }
}

It’s worth noting that this is just syntactic sugar. The prototype-based approach is still used under the hood. Although the outcome is the same I find ES2015 classes much more readable.

Modules

As projects become more complex it often becomes necessary to split your JavaScript into multiple files. Let’s expand on the previous example by adding a dependency to the Form class. The Errors class will be stored in a separate file:

// form.js
class Form {
    constructor( inputs ) {
        this.inputs = inputs;
        this.errors = new Errors();
    }
};

// errors.js
class Errors {};

Typically, when doing this you would manually include the scripts in your the page source. However, you would have to be careful about which order the files are loaded:

<script src="errors.js"></script>
<script src="form.js"></script>

Using modules you can export your classes and import them when needed.

// form.js
export default class Form {
    constructor( inputs ) {
        this.inputs = inputs;
        this.errors = new Errors();
    }
};

// errors.js
export default class Errors {};

Modules can export multiple named members but in this example, I’m exporting a single item. The default keyword is used to specify what will be exported when you don’t specifically ask for a named member of the module. In form.js you can then import the Errors class, like so:

 // form.js
import Errors from './errors.js';

export default class Form {
    constructor( inputs ) {
        this.inputs = inputs;
        this.errors = new Errors();
    }
};

You now only have to include the form.js file in your page source:

<script src="form.js"></script>

Writing small reusable components in JavaScript is nothing new, but previously a module loader such as AMD, CommonJS, or UMD was required – all of which had varying syntaxes. Thankfully, ES2015 modules introduce a standard approach to module loading.

Unfortunately, there is a ‘gotcha’ when it comes to ES2015 modules. Because no browsers currently support the import or export features, you need to use a module bundler such as rollup.js, webpack, or Babel. In the future browsers will automatically import any required dependencies.

Arrow Functions

Arrow functions are my favorite addition to ES2015. They have three main advantages:

Concise Syntax

The arrow function has a shorter syntax than a regular function. Let’s take a typical event handler where we pass a closure to the handler:

$( item ).on( 'click', function( event ) {
    console.log( 'Clicked!' );
} );

With arrow functions you can write a more concise version, like so:

$( item ).on( 'click', event => {
    console.log( 'Clicked!' );
} );

Notice how the function keyword has been removed. And because the closure doesn’t require any parameters you can simplify this further:

$( item ).on( 'click', () => {
    console.log( 'Clicked!' );
} );

If your callback requires multiple parameters you just wrap them in the parentheses:

$( item ).on( 'click', (event, someData, moreData) => {
    console.log( 'Clicked!' );
} );

It’s also possible to omit the curly braces entirely when the closure only contains a single line of logic:

$( item ).on( 'click', () => console.log( 'Clicked!' ) );

No Binding of this

Regular functions define their own this value. This is problematic when you are trying to access this of the parent context, for example from within a closure. It was common practice to save a reference to this:

var customComponent = {
    clickListener: function( item ) {
        var self = this;

        $( item ).on( 'click', function( event ) {
            self.handleClickEvent( event );
        } );
    },

    handleClickEvent: function( event ) {
        console.log( 'Item Clicked!' );
    }
};
customComponent.clickListener( 'body' );

This approach is no longer necessary when using arrow functions because this inherits the parent’s context.

var customComponent = {
    clickListener: function( item ) {
        $( item ).on( 'click', ( event ) => this.handleClickEvent( event ) );
    },

    handleClickEvent: function( event ) {
        console.log( 'Item Clicked!' );
    }
};
customComponent.clickListener( 'body' );

Implicit Returns

Implicit returns take the concise syntax one step further by allowing you to omit the return keyword. This is useful when writing one-line functions, typically within a closure. If you have an array of users and want to return their ages you could map over the array:

var users = [
    {
        name: 'John Doe',
        age: 32,
    },
    {
        name: 'Jane Doe',
        age: 31,
    }
];

users.map( function( user ) {
    return user.age;
} ); // [32, 31]

Using the concise syntax and implicit return you could rewrite this, like so:

users.map( user => user.age ); // [32, 31]

Arrow functions may seem somewhat cryptic at first glance, but once you get used to the new syntax you’ll soon appreciate their elegance. But, before you go crazy and replace all regular functions with arrow functions, remember that regular functions still have their place. For example, if you’re creating a simple click handler to toggle the display of an element you would still want this bound to the clicked element:

$( item ).on( 'click', function() {
    this.toggle();
} );

When Not to use an Arrow Function by Wes Bos is a good reference of when arrow functions aren’t appropriate.

Promises

Promises are used for asynchronous tasks where you may not know the return value straight away. A good example of this is when you submit an AJAX request but have to wait for a response from the server. Furthermore, the request may succeed or may fail. A promise allows you to handle both scenarios, regardless of when these events may occur. A simple way to think of a promise is:

I want to perform this action and when it completes do this, or, on failure do this.

The following delayedTask() function simulates an AJAX request. After 1 second it will randomly return a Promise that’s either been resolved or rejected. A resolved promise is one that completed successfully, whereas a rejected promise is one that failed. When dealing with AJAX requests anything other than a 200 response code from the server would usually be rejected:

function delayedTask() {
    return new Promise( ( resolve, reject ) => {
        setTimeout( () => {
            var rand = Math.floor( Math.random() * 3 ) + 1; // Random number between 1 - 3

            return rand === 2 ? reject() : resolve();
        }, 1000 );
    } );
}

You can then handle the result of the promise using the then and catch methods:

delayedTask() // I want to perform this task
    .then(() => alert( 'Look mom, I succeeded!' ) ) // when it completes do this
    .catch(() => alert( 'Boooo! I failed!' ) ); // on failure do this

Template Strings

In programming, it’s common to have to build a string interlaced with various data. In PHP, you can drop a variable directly into a double-quoted string:

$output = "User {$user->name} is {$user->age} years old.";

However, this wasn’t possible before ES2015. You either had to use string concatenation or a third-party helper library like handlebars:

var output = 'User ' + user.name + ' is ' + user.age + ' years old.'

Template strings (or template literals) greatly improve code readability and reduce the risk of incorrectly escaping strings. To create a template string, simply enclose your string within backticks. Variables should be prepended by a dollar sign and enclosed by curly braces:

var output = `User ${user.name} is ${user.age} years old.`;

You can also use template strings for multi-line strings, which prior to ES2015 required string concatenation.

console.log( `Line of text. 
Another line of text.` );

Template literals are a great addition!

Default Parameters

Default parameters have finally arrived in JavaScript! Prior to their introduction, you would have to manually check the value:

function incrementCount(count) {
    count = count || 0;

    return count + 1;
}

You can now specify the default value upfront like you do in PHP:

function incrementCount(count = 0) {
    return count + 1;
}

You can even pass a function as a default parameter. This is useful if you need to dynamically determine the value. In this example the default of 0 is returned, unless the user is on the /feelinglucky page:

function getDefaultValue() {
    if ( window.location.pathname === 'feelinglucky' ) {
        return Math.floor( Math.random() * 100 );
    }

    return 0;
}

function incrementCount( count = getDefaultValue() ) {
    return count + 1;
}

Pretty cool, right?

Browser Support

Everything covered in this article except for modules is supported in the current release of all major browsers. But, what about older browsers? Thankfully, Babel can be used to compile modern JavaScript into browser-compatible JavaScript. Babel does have a few caveats in regards to Internet Explorer 10 and below, but this should no longer be an issue with WordPress 4.8 dropping support for those browsers. There really is no reason you can’t use ES2015 today!

Conclusion

The new features in ES2015 will not only simplify your code, but make it more concise and readable. If you would like to learn more, check out the free 17-video ES2015 Crash Course on Laracasts. (nope, we weren’t paid to say that)

In the past, just the thought of writing JavaScript would bring on nightmares of jQuery spaghetti! However, ES2015 and frameworks such as Vue.js and React have changed that.

These days, I mostly prefer writing JavaScript over PHP, but maybe that’s because we still have to cater for PHP 5.2 in the WordPress ecosystem. Do you enjoy writing JavaScript? What’s your favorite addition in ES2015? Let us know in the comments below.

About the Author

Ashley Rich

Ashley is a Laravel and Vue.js developer with a keen interest in tooling for WordPress hosting, server performance, and security. Before joining Delicious Brains, Ashley served in the Royal Air Force as an ICT Technician.