Nov 2016
Before we can jump in and get our hands dirty we need to have a mechanism for converting this new ES6 JavaScript into a version that will run in the browser / node. This stage is called transpilation and we'll use a tool called gulp to run a transpiler called Babel.
The 2 most popular transcompilers at the moment are Babel and Traceur. In this post we’ll be using the Babel transcompliler to convert our ES6 JavaScript to ES5 Javascript, which will ensure that it can run in any JavaScript engine.
npm init
npm install --save-dev gulp-babel babel-preset-es2015
Then create a file in your project called .babelrc
and paste this text into it:
{ "presets": ["es2015"] }
With this file the Babel transcompiler will know that you’re using ES6.
Gulp is my go to build tool and we’ll use it to run the Babel transcompiler. Install Gulp by running:
$ sudo npm install -g gulp
You need to install Gulp globally once. Then, for each project, you’ll need a local Gulp. So from the root directory of your project run
$ npm install --save-dev gulp
Create the file gulpfile.js and stick the following in there
const gulp = require('gulp');
const babel = require('gulp-babel');
gulp.task('default', function(){
// node source
gulp.src("es6/**/*.js")
.pipe(babel())
.pipe(gulp.dest("dist"));
// browser source
gulp.src("public/es6/**/*.js")
.pipe(babel())
.pipe(gulp.dest("public/dist"));
});
Now we’ll create a .js file and check that we can transcompile and run it. So create a directory called es6 and in there create a file called test.js
and paste in the following simple code:
'use strict';
const sententaces = [
{subject: 'ES6', verb:'is', object:'sweet'},
{subject: 'ES5', verb:'is so', object:'last year'}
];
function say({subject,verb,object}){
console.log(`${subject} ${verb} ${object}`);
}
for(let s of sententaces){
say(s);
}
run gulp with:
$ gulp
the transcompiled code has been saved to the dist directory (we told it to do this in the gulpfile) and you can run it with the following command:
$ node dist/test.js
Your output should look like this:
ES6 is sweet
ES5 is so last year
There we go; our first bit of ES6. Before we go any further, we’ll add a watcher to our gulp stack. This will watch for changes to our ES6 files and automagically run gulp, saving us the hassle.
const gulp = require('gulp');
const babel = require('gulp-babel');
gulp.task('default', function(){
// node source
gulp.src("es6/**/*.js")
.pipe(babel())
.pipe(gulp.dest("dist"));
// browser source
gulp.src("public/es6/**/*.js")
.pipe(babel())
.pipe(gulp.dest("public/dist"));
});
gulp.task('watch', ['default'], function () {
gulp.watch(["es6/**/*.js", "public/es6/**/*.js"], ['default']);
});
Now you can just run $ gulp watch
to have Babel run whenever gulp detects a change to any .js files in the es6 and public/es6 directories (if you notice that your transcompiled javascript is stale check that the gulp watcher hasn't crashed).
The value of a variable can change at any time whereas the value of a constant is assigned when it is created and cannot be changed after that. When you don’t need to change the value of something then get into the habit of using const
. If you do need to reassign the value then use the new keyword let
in favour of the old var
. The difference is very subtle but will save you from the occasional nightmare of a debug scenario like this:
function varTest() {
var x = 1;
if (true) {
var x = 2; // same variable!
console.log(x); // 2
}
console.log(x); // 2
}
Using let
instead generally gives you the behaviour you’d expect:
function letTest() {
let x = 1;
if (true) {
let x = 2; // different variable
console.log(x); // 2
}
console.log(x); // 1
}
In the real world, this is a particular issue when executing asynchronous code. Take the following simple example:
var i;
for (i = 3; i >= 0; i--) {
setTimeout(function () {
console.log(i);
});
}
// prints out
// -1
// -1
// -1
// -1
Because the function getting passed to setTimeout
is invoked at some point in the future, by the time it runs i
is -1. The ES5 work around is to use an additional function to create a new scope, which captures the value of i
in a closure.
// old-school workaround using a closure and an IIFE (immediately invoked function expression)
var i;
for (i = 3; i >= 0; i--) {
(function (i) {
setTimeout(function () {
console.log(i);
});
})(i);
}
// prints out
// 3
// 2
// 1
// 0
This is a pain in the ass and pretty fatiguing on the developer. Block-scoped variables solve this problem for us:
// Use 'let' instead
for (let i = 3; i >= 0; i--) {
setTimeout(function () {
console.log(i);
});
}
// prints out
// 3
// 2
// 1
// 0
This feature lets you take an object or array and destructure it into individual variables.
Create the file es6/destructuring.js :
"use strict";
const obj = {b:2, c:4, d:"hello, world"};
const{a,b,c} = obj;
console.log(a);
console.log(b);
console.log(c);
Run $ node dist/destructuring.js
and you'll get the following output:
undefined
2
4
Note: For brevity, I’ll now stop telling you to create file abc.js and run it through node. You get the idea!
You can also destructure arrays:
const arr = ["I'm", "an", "array"];
let [x,y] = arr;
console.log(x); // I'm
console.log(y); // an
The spread operator lets you take all the remaining elements. Note that this will work on arrays but not objects:
let [z, ...theRest] = arr;
console.log(z); // I'm
console.log(theRest); // [ 'an', 'array' ]
We can even do this stuff on function args!
function average({a, b}){
return (a + b) / 2;
}
const o = {
a: 4,
b: 6
};
average(o); // 5
Note that the identifiers in the supplied object must match up with the argument names in the function.
This is a small addition to the language, compared to some of the other new features, but I really like how it makes writing strings neater:
"use strict";
const name = "ninjaPixel",
o = {
day: {
ofWeek: "Sunday",
ofMonth: "9"
},
month: "October"
};
function ordinalIndicator(x) {
switch (x) {
case 1:
return "st";
case 2:
return "nd";
case 3:
return "rd";
default:
return "th";
}
}
const out = `This is ${name}'s blog. It was written on ${o.day.ofWeek} ${o.day.ofMonth}${ordinalIndicator(o.day.ofMonth)} of ${o.month}`;
console.log(out);
This isn't anything that we couldn't have done in ES5, it's just neater. Note how we use ${...}
to access variables and functions and the whole template string is encapsulated in backticks.
This is ninjaPixel's blog. It was written on Sunday 9th of October
This is a seriously handy feature. With ES5 you'd have to explicitly handle undefined
args and assign default values, within your function block. Specifying these default values when you define your function is much neater and it's also much easier to understand when reading someone else's code.
function x(a, b = "Vote Pedro!", c = 1){
return `${a} - ${b} - ${c}`;
}
x(1,2,3); // "1 - 2 - 3"
x(1,2); // "1 - 2 - 1"
x(1); // "1 - Vote Pedro! - 1"
x(); // "undefined - Vote Pedro! - 1"
Officially called 'arrow notation', fat arrows basically save you from typing boilerplate code when declaring functions. There is also the benefit that this
is lexically scoped within arrow functions, saving you from having to do the classic var self = this
trick that you see all over the shop in ES5.
const func1 = function(){return "Hello, world!";};
// or with fat arrows:
const func1 = () => "Hello, world!";
const func2 = function(name){return `Hello, ${name}`;};
// or with fat arrows:
const func2 = (name) => `Hello, ${name}`;
const func3 = function(x, y) {return x + y;};
// or with fat arrows:
const func3 = (x, y) => x +y;
const o = {
name: "Matt",
greet:
}
"use strict";
const skinny = {
name: "matt",
greet: function(){
function capitaliseName(){
return this.name.charAt(0).toUpperCase() + this.name.slice(1);
}
return `Hello, ${capitaliseName()}!`;
}
};
const fatty = {
name: "matt",
greet: function(){
const capitaliseName =() => {
return this.name.charAt(0).toUpperCase() + this.name.slice(1);
}
return `Hello, ${capitaliseName()}!`;
}
};
console.log(skinny.greet()); // Cannot read property 'name' of undefined
console.log(fatty.greet()); // Hello, Matt!
Note that you can omit the return statement and curly braces when the function body is a single expression.
Yes. Finally. JavaScript has OOP.
Rather than go into detail about what OOP programming is (there are already loads of better blogs on that), let's jump straight into using it:
class CrapSuperhero {
constructor(name, power) {
this._name = name;
this._power = power;
this._clothes = 'casual';
}
run() {
console.log(`${this._name} is running`);
}
// we can use getters and setters to protect the way that internal variables are written to
get clothes() {
return this._clothes;
}
set clothes(clothes) {
if (typeof clothes === 'string') {
this._clothes = clothes;
} else {
throw new Error('Clothes must be typeof string');
}
}
// static method
static getNextAvailableSuperheroID() {
return CrapSuperhero.nextID++;
}
}
CrapSuperhero.nextID = 0;
// instantiation
const manSpider = new CrapSuperhero('man spider', 'Overly hairy legs');
const manIron = new CrapSuperhero('man iron', 'Expert at ironing shirts');
manSpider.run(); // man spider is running
manIron.run(); //man iron is running
// using getters and setters
manIron.clothes = 'steam iron';
manSpider.clothes = function () {}; // Error: Clothes must be typeof string
// call static method
const id = CrapSuperhero.getNextAvailableSuperheroID(); // 0
class Animal {
constructor(name) {
this._is_motile = true; // can move about the place
this._has_rigid_cell_walls = false;
this._is_heterotrophic = true; // digests food internally
this._name = name;
}
respondToStimuli(stimuli) {
console.log(`${this._name} is responding to ${stimuli}`);
}
move() {
console.log(`${this._name} is moving`);
}
}
class Mammal extends Animal {
constructor(name) {
super(name);
this._has_sweat_glands = true;
this._has_jaw_joint = true;
}
regulateTemperature() {
console.log(`${this._name} is using hair to regulate temperature`);
}
set fur(color){this._fur =`${color} fur`;}
get fur(){return this._fur;}
}
class Gorrilla extends Mammal {
constructor(name, color) {
super(name);
this.fur=color;
this._endangered = true;
}
hunt(prey) {
console.log(`${this._name} is hunting ${prey}`);
}
move() {
console.log(`${this._name} is walking on knuckles`);
}
}
const harambe = new Gorrilla('Harambe', 'silver');
harambe.respondToStimuli('cold temperature'); // Harambe is responding to cold temperature
harambe.regulateTemperature(); // Harambe is regulating temperature
harambe.move(); // Harambe is walking on knuckles
harambe.fur; // silver fur
for(let p in harambe){
console.log(`${p} = ${harambe[p]}`);
}
// output:
_is_motile = true
_has_rigid_cell_walls = false
_is_heterotrophic = true
_name = Harambe
_has_sweat_glands = true
_has_jaw_joint = true
_fur = silver fur
_endangered = true
Every object ultimately inherits from Object
. The toString
method returns [object Object]
which isn't particularly useful. However, you can overide this method simply like so:
class HelloWorld {
constructor(){
this.prop1='Hello';
this.prop2=', World!';
}
toString(){
return `prop1: ${this.prop1}. prop2:${this.prop2}`;
}
}
let hw = new HelloWorld();
hw.toString(); // prop1: Hello. prop2:, World!
Justin Fagnani's post on ES5 mixins is phenomenal and he comes up with a really nice way to descriptively assign them to your class. Check his blog out for a great write up. I've pasted the crux of it below:
// from http://justinfagnani.com/2015/12/21/real-mixins-with-javascript-classes/
let Mixin1 = (superclass) => class extends superclass {
foo() {
console.log('foo from Mixin1');
if (super.foo) super.foo();
}
};
let Mixin2 = (superclass) => class extends superclass {
foo() {
console.log('foo from Mixin2');
if (super.foo) super.foo();
}
};
class S {
foo() {
console.log('foo from S');
}
}
class C extends Mixin1(Mixin2(S)) {
foo() {
console.log('foo from C');
super.foo();
}
}
new C().foo();
Which outputs:
from C from Mixin1 from Mixin2 from S
let mix = (superclass) => new MixinBuilder(superclass);
class MixinBuilder {
constructor(superclass) {
this.superclass = superclass;
}
with(...mixins) {
return mixins.reduce((c, mixin) => mixin(c), this.superclass);
}
}
class MyClass extends mix(MyBaseClass).with(Mixin1, Mixin2) {
/* ... */
}
This enables us to now write:
class C extends mix(S).with(Mixin1, Mixin2) {
foo() {
console.log('foo from C');
super.foo();
}
}
To make key-value pairs is ES5 you'd use an object. ES6 brings us a data structure exclusively for doing this. maps
overcome the following drawbacks of using objects for key-value pairs:
const user1 = {id:1, name:'Bob';};
const user2 = {id:2, name:'Mary';};
const roles = new Map();
roles
.set(user1, 'Admin')
.set(user2, 'Trial');
roles.get(user2); // 'Trial'
roles.has(user1); // true
roles.get({foo:'bar'}); // undefined
roles.size; // 2
Like an array but duplicates are not allowed. Continuing from our users and roles example, it wouldn't make sense if a given user had the same role multiple times, so we could use a set
instead:
const possibleRoles = new Set();
possibleRoles
.add('Trial')
.add('Admin'); // Set['Trial', 'Admin']
// you don't need to check if an item already exists before adding it
possibleRoles.add('Trial'); // nothing happens. Set['Trial', 'Admin']
possibleRoles.size; // 2
possibleRoles.delete('Admin'); // true is returned as the item existed in the set