ES6 - Part Two
In my previous article (ES6 - Part One) I highlighted some of the most anticipated new features in ECMAScript 2015 (AKA ES6).
This article will continue that theme, covering some of the more advance features.
Classes
As described in my article “JavaScript - Part Three”, JavaScript is a prototype oriented language (also known as classless). Thanks to the flexibility of functions, you have always been able to simulate classes, without the class keyword.
If JavaScript is your first programming language or you have previous experience with prototypical inheritance, the lack of the class keyword is not really a limitation, however, if you are familiar with class-based languages, JavaScript can be quite confusing.
As a result, ES6 officially introduces the class keyword, which provides a simpler syntax to create objects and deal with inheritance. However, the class syntax does not introduce a new object-oriented inheritance model to JavaScript.
The easiest way to think about a class is to consider it a “special function”, which includes class declarations and class expressions.
An example of a class declaration can be found below:
class Square {
constructor(height, width) {
this.height = height;
this.width = width;
}
};
The example below is the same code, using a class expression:
var Square = class Square {
constructor(height, width){
this.height = height;
this.width = width;
}
};
This class expression is named (e.g. class Square
), however class expressions can also be unnamed.
Finally, it is important to note that class declarations and class expressions are not hoisted (unlike functions), which means you must first declare your class, before you access it.
Promises
Thanks to the increased performance and flexibility, asynchronous code is rapidly gaining popularity. In short, “async” enables you to trigger numerous requests simultaneously and then handle the responses as they become available without waiting.
A JavaScript promise facilitates asynchronous code and in many respects, is just like a real promise. For example, I could promise to buy you a new Mac next week. At the time the promise is made you don’t actually have the new Mac and you won’t know if the promise is fulfilled until next week.
As a result, a promise can have three states:
Promise Pending: You are waiting to see if the promise is fulfilled.
Promise Resolved: You actually get a new Mac at the end of the week.
Promise Rejected: You don’t get a new Mac at the end of the week.
An example of a promise being created can be found below:
var newMac = true;
var willIGetNewMac = new Promise(function(resolve, reject) {
if(newMac) {
resolve('Success!');
}
else {
reject('Failure!');
}
});
In the example, When the result is successful, the resolve(success_value)
is called, but if the result fails, the reject(fail_value)
is called.
An example of a promise being consumed can be found below:
var askMatt = function () {
willIGetNewMac
.then(function (fulfilled) {
console.log(fulfilled);
})
.catch(function (error) {
console.log(error.message);
});
};
askMatt();
In the example, we have a function call askMatt
, which consumes the promise willIGetNewMac
. Once the promise has been resolved or rejected, action is taken by using .then
(success_value) and .catch
(fail_value).
Modules
Modules in JavaScript are not new, but they were previously implemented via libraries. ES6 supports built-in modules, with each module being defined in its own file. However, the variables and/or functions defined in a module are not visible outside of the module unless explicitly exported.
An example of a module (filename utility.js) can be found below:
function generateNumber() {
return Math.random();
}
function sum(a, b) {
return a + b;
}
export { generateNumber, sum }
The export
keyword exports the two functions GenerateNumber
and sum
.
To import the functions, you must include the following syntax, where “utility” is the name of the file (e.g. utility.js):
import { generateRandom, sum } from 'utility';
You can also import the entire module as an object and access exported values as properties:
import 'utility' as utils;
The use of modules can dramatically simplify complex applications, therefore is highly recommended.
Rest Parameters
The rest parameter syntax allows you to represent a variable number of arguments as an array. As the rest parameter is an instance of an array, all of the array methods work as normal.
The example below highlights how a function with a variable number of arguments was handled prior to ES6 (using the arguments object):
function listCars() {
var car = Array.prototype.slice.call(arguments);
cars.forEach(function(car) {
ChromeSamples.log(car);
});
}
listCars('McLaren', 'Lamborghini', 'BMW', 'Mercedes');
The example below achieves the same result, using the rest operator (...
):
function listCarsES6(...cars) {
cars.forEach(function(car) {
ChromeSamples.log(car);
});
}
listCarsES6('McLaren', 'Lamborghini', 'BMW', 'Mercedes');
It is worth noting that there can only be a single rest parameter for a given function. For example, listTransport(…cars, …boat) would not work.
Spread Operator
The main objective of the spread operator is to spread the objects of an array. For example, a common use case is to spread an array into the function arguments, which prior to ES6 would have been achieved using Function.prototype.apply
:
function foo(x, y, z) { }
var args = [0, 1, 2];
foo.apply(null, args);
With ES6 you can achieve the same result by prefixing the arguments with (...
):
function foo(x, y, z) { }
var args = [0, 1, 2];
foo(...args);
This example will spread the args array into positional arguments.
Map
A map is a new data structure introduced in ES6. A map allows you to store data using a key, then retrieve the data by passing in the key. In short, a map is a HashTable (often described as a Dictionary in C#).
Looking at the description of a map, you might be thinking that it sounds very similar to an object. However, there are a few key differences.
- Any value (both objects and primitive values) may be used as either a key or a value.
- Keys are not converted to strings (when using objects as maps, the keys are converted to strings).
- It is very easy to get the size of a map.
An example of a map can be found below, including the set
method which sets the value for the key in the map:
var myMap = new Map()
map.set('Mac', { description: 'A personal computer' })
map.set('iPhone', 'Smartphone'})
As previously mentioned, one advantage of a map is that you can use anything for the keys, including functions, objects, etc.
The example below uses the get
method to return the specific element from the map:
myMap.get('iPhone');
This example will return “Smartphone”.
I recommend using maps over objects when the keys are unknown until runtime and when all keys are the same type, and all values are the same type. Use objects when there is logic that operates on individual elements.