Upgrade your JavaScript

September 2, 2019

In 2019, most of my personal (web) projects are written in TypeScript, because well, it's just an awesome extension of plain old JavaScript.

Many developers I know agree, and you could even say, "Typescript won", regarding competing projects like CoffeeScript, Dart, or even plain JS.

To re-capture ECMAScript (the JavaScript specification) and its relation to TypeScript, have a look at the following image (from this blog post):

You can read it like follows:

  • ES5: "old" JavaScript (2009)
  • ES6: "newer" Javascript (2015)

Using ES 6

Sometimes, when "pure" JavaScript is needed or I'm working on something that can not be TypeScript (because it has to be supported directly by the browser), I mostly see old, hacky JavaScript, and am tempted to also write in the same old, ES5 style.

But which version is safe to use? This compatibility table has a detailed overview for all ES 6 features and their support in the different browsers (and backend servers like Node).

To put it simple: All current major browsers have full support (> 95%) for ES 6. Here is a table which shows when the Browser started support for ES 6 (from w3schools):

Browser Version Date
Chrome 51 May 2016
Firefox 54 Jun 2017
Edge 14 Aug 2016
Safari 10 Sep 2016
Opera 38 Jun 2016

So, definitely time to start using ES6!

Features

The excellent page ES 6 features has a nice, extensive list of features, from which I'd like to point a few out.

Classes

Finally, "real" classes!

class Pet {
  constructor(name) {
    this.name = name;
  }

 greet() {
    console.log(`Hi, my name is ${this.name}`);
  }
}

You can create new instances just as in any other language:

let pet = new Pet("Bello");
pet.greet(); // output: Hi, my name is Bello

Block-scoped variables with let/const

Declare constants and variables without exposing them globally.

const PI = 3;

for (let i = 0; i < 2; i++) {
  console.log(i);  // output: 0 1
}
// at this point, i is not defined
console.log(i); // output: "ReferenceError: can't access [...] `i' before initialization"
let i = 5;
console.log(i); // output: 5

Arrow functions (for “this”….)

No more var that = this, just use fat arrow function expressions and preserve context. They also come in handy for many Array functions which expect a function callback:

let arr = ['foo', 'bar', 'foobar'];
let filtered = arr.filter(entry => entry.includes('bar'));
console.log(filtered); // output: ['bar', 'foobar']

String interpolations

As you can see with the class above, you can easily use string interpolation to insert a variable into a defined string constant:

let name = 'Mary';
console.log(`Hi, my name is ${name}!`); // output: Hi, my name is Mary!

Default params

You can define default params for functions:

function greet(name = 'you') {
  console.log(`Hello, ${name}`);
}

console.log(greet()); // output: Hello, you
console.log(greet('Daniel')); // output: Hello, Daniel

Array spread

The new spread syntax lets you easily turn an array into multiple single values, for example in a function call:

function add(a, b, c, d) {
  return a + b + c + d;
}

let arr = [1, 1, 2, 3];
console.log(add(...arr)); // output: 7

Import/export modules

After community driven modules (CommonJS and AMD/RequireJS), ES6 now supports modules itself, so it is easier to use and reuse code. You can either import exported members from certain modules, or import whole modules:

import defaultExport from "module-name";
import * as name from "module-name";

You can read a detailed explanation of modules here.

Set and Map

ES6 has native support for Set and Map data structures:

let s = new Set();
s.add(1);
s.add(2);
s.add(1); // if a value already exists, nothing will happen
console.log(s.size); // output: 2
console.log(s.has(2)); // output: true
s.clear(); // set is now empty again

An example with Map:

let m = new Map();
m.set('a', 1);
m.set('b', 2);
m.set('a', 3); // we can override an existing value
console.log(m.has('b')); // output: true
console.log(m.get('a')); // output: 3
m.clear(); // map is now empty again

Object.assign()

To clone or merge an object, use Object.assign():

let obj1 = {a: 1, b: 2};
let obj2 = Object.assign({a: 0}, obj1);
console.log(obj2); // output: {a: 1, b: 2}

Array.find

As other Array functions, find() will accept a function, and will return the first element found for which the function returns true:

let arr = [1, 1, 2, 3, 5, 8, 11];
console.log(arr.find(e => e % 2 == 0)); // output: 2

String.startsWith/endsWith/includes

Finally some more readable alternatives to indexOf:

let name = 'JavaScript';
console.log(name.startsWith('Java')); // output: true
console.log(name.endsWith('Java')); // output: false
console.log(name.includes('Java')); // output: true

Promises

ES6 has built-in support for Promises:

let flag = true;
let myPromise = new Promise((resolve, reject) => {
  if (flag) {
    resolve(`Flag: ${flag}`);
  } else {
    reject('Flag was set to false :-(');
  }
});

myPromise.then(msg => console.log(msg)); // output: Flag: true