Welcome to part two of ECMAScript 6: Why Should I use it? If you missed part one, you can find it here. This time, we’ll be looking at the following features that ECMAScript 6 brings to the developer:
Enhanced Object Properties
With ECMAScript 6, we can no use a shorthand syntax to define object properties like so:
var a = 5, b = 10; // ECMAScript 5 var myObject = { a: a, b: b }; // { a: 5, b: 10 } // ECMAScript 6 var myObject = { a, b }; // { a: 5, b: 10 }
It also supports computed object property names. What am I talking about? Take a look:
var name = 'Two'; // ECMAScript 5 var myObject = { propertyOne: 5 }; myObject['property' + name] = 10; /* myObject -> { propertyOne: 5, propertyTwo: 10 } */ // ECMAScript 6 var myObject = { propertyOne: 5, [ 'property' + name ]: 10 }; /* myObject -> { propertyOne: 5, propertyTwo: 10 } */
We can even run functions or whatever we’d like inside the property name:
function getPrefix() { return 'my'; } var myObject = { [getPrefix() + 'PropertyOne']: 5, [getPrefix() + 'PropertyTwo']: 10 }; // myObject -> { myPropertyOne: 5, myPropertyTwo: 10 }
We can even define methods using a ‘class-like’ syntax, here’s what it looks like:
// ECMAScript 5 var myObject = { sayHello: function(name) { console.log('Hello ' + name + '!'); }, sayGoodbye: function() { console.log('Goodbye!'); } }; myObject.sayHello('John'); // 'Hello John!' // ECMAScript 6 var myObject = { sayHello(name) { console.log('Hello ' + name + '!'); }, sayGoodbye() { console.log('Goodbye!'); } } myObject.sayHello('John'); // 'Hello John!'
These shorthand methods don’t bring ground breaking functionality to the table, but they provide invaluable syntactic sugar!
De-structuring Assignments
De-structuring assignments are awesome. They come off a bit confusing at first but oh boy, will you learn to love them. One of the most common uses is array matching. We can assign array values to variables without having to explicitly use their index. Cutting to the chase, it’s easier to see it in action:
var myArray = [1, 2, 3, 4]; // ECMAScript 5 var a = myArray[0], b = myArray[1], c = myArray[2], d = myArray[3]; // ECMAScript 6 var [a, b, c, d] = myArray;
Isn’t that great? We can assign each value to their variable using this notation. We can also skip values if need be:
var myArray = [1, 2, 3, 4]; // ECMAScript 5 var a = myArray[0], c = myArray[2]; // ECMAScript 6 var [a, , c] = myArray;
In this last example, we are only assigning values to a and c.
Similar to array matching, we can also use object matching. They are both fairly similar, here’s what object matching looks like:
function getPerson() { return { first: 'John', last: 'Doe', phone: '555 555 555' } } // ECMAScript 5 var person = getPerson(), first = person.first, last = person.last, phone = person.phone; // ECMAScript 6 var { first, last, phone } = getPerson();
This can be extended to account for deeper object levels, or using custom variable names:
function getPerson() { return { name: { first: 'John', last: 'Doe' }, phone: '555 555 555', city: 'New York' } } // ECMAScript 5 var person = getPerson(), first = person.first, last = person.last, phone = person.phone, city = person.city; // ECMAScript 6 var { name: { first: a, last: b }, phone: c, city: d } = getPerson();
There’s is a few more things you can do with de-structuring assignments, such as assigning default values on assignment:
var myArray = [10, 20, 30]; var [a = 1, b = 2, c = 3, d = 4, e] = myArray; // a -> 10 // b -> 20 // c -> 30 // d -> 4 (uses default value!) // e -> undefined (we didn't define a default value)
We may also use parameter matching when defining our functions:
// ECMAScript 5 function sum(obj) { return obj.x + obj.y; } sum({ x: 5, y: 10 }); // ECMAScript 6 function sum({ x, y }) { return x + y; } sum({ x: 5, y: 10 }); // OR function sum({ first: x, second: y }) { return x + y; } sum({ first: 5, second: 10 });
// ECMAScript 5 function sum(array) { return array[0] + array[1]; } sum([5, 10]); // ECMAScript 6 function sum([x, y]) { return x + y; } sum([5, 10]);
Modules
Modules lets you create complex applications with multiple files using a much simpler syntax and avoiding unnecessary pollution on the global namespace. This next example is quite absurd, since an application as simple as this one would never require more that a module, but it shows how modules can be used.
Here’s the ECMAScript 5 version (this is not the best way to do it, but it is the easiest to understand):
UtilityLibrary = {}; UtilityLibrary.sayHi = function(name) { console.log('Hi ' + name + '!'); }; UtilityLibrary.sayBye = function() { console.log('Goodbye!'); };
var myName = 'John'; var utils = UtilityLibrary; utils.sayHi(myName); utils.sayBye();
The ECMAScript 6 version would look like:
export function sayHi(name) { console.log('Hi ' + name + '!'); } export function sayBye() { console.log('Goodbye!'); }
// Option 1 import { sayHi, sayBye } from 'utilities'; var myName = 'John'; sayHi(myName); sayBye(); // Option 2 import * as utils from 'utilities'; var myName = 'John'; utils.sayHi(myName); utils.sayBye();
We can also export/import a default from a module, it would look like this:
export default function sum(a, b) { return a + b; }
import sum from 'utilities'; // No curly braces here! sum(5, 10); // 15
Classes
ECMAScript 6 comes with a more intuitive class syntax, moving away from the prototypical inheritance pattern that we’ve been using until now. Here’s a simple class, using both ES5 and 6:
// ECMAScript 5 var Greeter = function(name) { this.name = name; } Greeter.prototype.greet = function() { console.log('Hello ' + name + '.'); } var johnGreeter = new Greeter('John'); johnGreeter.greet(); // Hello John. // ECMAScript 6 class Greeter { constructor(name) { this.name = name; } greet() { console.log('Hello ' + name '.'); } } var johnGreeter = new Greeter('John'); johnGreeter.greet(); // Hello John.
Classes come with an easier to use, more intuitive inheritance syntax, similar to other OOP languages:
class Pet { constructor(name) { this.name = name; } getName() { return this.name; } } class Cat extends Pet { constructor(name, breed) { super(name); this.breed = breed; } sayMeow() { console.log('Meow! I'm a ' + this.breed + '!'); } } var jack = new Cat('Jack', 'Siamese'); jack.getName(); // 'Jack' jack.sayMeow(); // 'Meow! I'm a Siamese!'
We can access the base class using super within the child:
class StoreItem { constructor(barcode) { this.barcode = barcode; } getLabel() { return 'BARCODE:' + this.barcode; } } class FoodItem extends StoreItem { constructor(barcode) { super(barcode); // We call the base class constructor } getLabel() { return 'FOOD-' + super.getLabel(); } } var banana = new FoodItem('555-555-ABC'); console.log(banana.getLabel()); // 'FOOD-BARCODE:555-555-ABC'
With classes, we also get things like static members:
class Pet { constructor(name, species) { this.name = name; this.species = species; } static examplePet() { return new Pet('Fluffy', 'cat'); } sayHi() { console.log(this.name + ' the ' + this.species + ' says hi!'); } } var aCat = Pet.examplePet(); var anotherCat = new Pet('Peter', 'cat'); aCat.sayHi(); // 'Fluffy the cat says hi!' anotherCat .sayHi(); // 'Peter the cat says hi!'
We can also define setters and getters:
class Cat { constructor(name) { this._name = name; } get name() { return this._name; } set name(newName) { this._name = newName; } } var myCat = new Cat('Peter'); console.log(myCat.name); // Peter myCat.name = 'Joe'; console.log(myCat.name); // Joe console.log(myCat._name); // Joe
Promises
Ah the joy. Promises let us wait for asynchronous code to finish executing. This is very useful when dealing with AJAX requests or timeouts. An example of asynchronous code without a promise would be:
var name; console.log('Hello'); setTimeout(function() { name = 'Joe'; }, 10); console.log('Hey ' + name); // Hello // Hey
Since the timeout is executed asynchronously, name does not get assigned until the second console.log() has already executed. If we used a promise:
var name; console.log('Hello'); var namePromise = new Promise((resolve, reject) => { setTimeout(function() { name = 'Joe'; resolve(); // We could call reject() if an error happened! }, 10); }); namePromise.then(() => { // Called when resolve() happens. console.log('Hey ' + name); }, () => { // Called when reject() happens. }); // Hello // Hey Joe
We could also change the previous example to call resolve() with a value:
console.log('Hello'); var namePromise = new Promise((resolve, reject) => { setTimeout(() => { resolve('Joe'); }, 10); }); namePromise.then((name) => { console.log('Hey ' + name); }, () => { console.error('An error happened!'); }); // Hello // Hey Joe
We may even create and wait for several promises to complete before executing more code:
var promiseOne = new Promise((resolve, reject) => { setTimeout(() => { resolve('Cat'); }, 300); // 300ms timeout }); var promiseTwo = new Promise((resolve, reject) => { setTimeout(() => { resolve('Dog'); }, 100); // 100ms timeout }); var promiseThree = new Promise((resolve, reject) => { setTimeout(() => { resolve('Hippo'); }, 200); // 200ms timeout }); Promise.all([ promiseOne, promiseTwo, promiseThree, ]).then((data) => { let [ one, two, three ] = data; // De-structuring assignment! 😀 console.log(one); console.log(two); console.log(three); }, (error) => { console.error('An error happened: ' + error); }); // Cat // Dog // Hippo
As you can see, even if the timeout where different, we are waiting for all of them and getting an array (data) with each resolve item in the exact order we defined!
This concludes part two of ECMAScript 6: Why should I use it?, if you missed part one, you can find it here. There’s a lot more to ES6 than what you see presented in this series, you can find additional documentation in here. If you think there’s are more features that you’d like to see here, don’t hesitate, shoot me an email, ping me on twitter or post a comment below!