ES6: Practical Bits

Author: Steve Venzerul

It's finally landed. on June 18th, 2015 TC39 approved ES6 .

State of the Union

Conclusion: Use Babel, unless you're a bleeding edge Windows user, in which case, you can try going native with the Edge browser.

Image credit: http://kangax.github.io/compat-table/es6/

Const and Let

ES6 brings proper constants and block scoping with the Let and Const keywords. 

//Making something truly constant in ES5 requires this:
Object.defineProperty(typeof global === "object" ? global : window, "PI", {
    value:        3.141593,
    enumerable:   true,
    writable:     false,
    configurable: false
})
PI > 3.0;

//Yuck. Versus ES6's:
const PI = 3.1415;

//Have you ever done this?
for (var i = 0; i < 10; i++) {
  console.log(i);
  for(var i = 0; i < 20; i++) {
    console.log(i);
  }
}
//Only to realize your second loop is not executing correctly?
//If we simply change the 
var i = 0; 
//to
let i = 0;
//BOOM, everything works correctly because the inner i gets it's own proper scope.

Default Parameters

// ES6
function fib(n = 1) {
  if (n === 1 || n === 0) return n;
  return fib(n - 1) + fib(n - 2);
}

// Defaults can be fairly complex
function defaults(opts = {a: 'prop1', b: 'prop2'}) {
  return opts;
}

// (Defaults + destructuring) === Awesome
function defaultsDes({a = 1, b = 2, c = 3} = {}) {
  return {a, b, c}; //Uses the new object shorthand syntax.
}
console.log(defaultsDes());
//output: {"a":1,"b":"blah","c":"superblah"}

//Madness
function defaultWithArray(arg, [a = 1, b = 2, c = 3] = rest) {
  console.log(arg);
  console.log(a, b, c);
}
defaultWithArray('arg', ['a', 'b', 'c']);

Default params finally let us write code free of hacks like using the || operator, or using third party libs like underscore.js's _.defaults().

As the last example shows, default params + destructuring completely replaces the need for .defaults(). 

Rest Parameters

//How many times have you done this?
function old(arg1 /*, more params */) {
  let rest = Array.prototype.slice.call(arguments, 1);
  //do something with the rest of the args.
}

//Now you can just go like this:
function withRest(arg1, ...rest) {
  let secondArg = rest.shift();
  console.log(secondArg);
}

console.log(withRest('first', 'second', 'third', 'fourth', 'fifth'));
//Output: second

//Hell, with rest params you don't even need to manipulate the arguments pseudo-array
//with the ol'
let args = Array.prototype.slice.call(arguments);
//and should generally avoid it in favour of the ... operator which yields a real array.

Spread

//Wanna find the max of an array?
let arr = [3,7,5,7,8,9,0,4,5,6];
console.log(Math.max.apply(null, arr));

//We can now do this
console.log(Math.max(...arr));


//Or how about this?
$.get('http://example.com/api/endpoint', function(response) {
    let message = response[0];
    let data = response[1];
    let err = response[2];
});

//Much more succinct
let [message, data, err] = [...response];


//Array concat goes away
let a = [1,2], b = [3,5,6];
let c = a.concat(b);
console.log(c);

//Now we do this
let d = [...a, ...b];
console.log(d);

Destructuring

//destructuring objects
let {a, b} = {a: 'val1', b: 'val2', c: 'ignored', d: 'ignored1'};
console.log(a, b);

let {top: {deep, superDeep: {val} } } = {
  top: {
    deep: 'value', superDeep: {
      val: 'str' 
    } 
  }
};
console.log(deep, val);

//destructuring arrays
let [c, d] = [1, 2];
console.log(c, d);

//skipping values
let [e, , f] = [1, 2, 3];
console.log(e, f);

function goCrazy([name, val]) {
  console.log(name, val);
}
goCrazy(['myname', 'myvalue']);

Destructuring is sweet sugar for breaking up objects and arrays into their components, without resorting to being verbose.

Template Strings

let a = 10, b = 5;
console.log('Fifteen is' + (a + b) + 'and \n not' + 2 * a + b);
console.log(`Fifteen is ${a + b} and\nnot ${2 * a + b}.`);

//Multi line by default
let message = `This is a multi-line string that you would have
had to write with weird '\\' and '+' signs. But no mo! Now you can
write multi-line strings the way nature intended.`
console.log(message);

//Guys, we even got unicode code points. Holy batman!
console.log(`\u{1f449}`);

//You can get pretty crazy with the substitution syntax as it accepts
//any valid expression. Using an array comprehension here.
console.log(`This is an array ${[for (x of [1,2,3,4,5,6,7,8]) if (x%2) x]}`);

Gone are the days of .template(), Handlebars and friends. Template string are here to save us.

Tagged Template String

Tagged templates let us process the templates with arbitrary functions.

//Tagged template strings
let msg = escapeHtml`someone being sneaky ${'<script>alert(1)</script>'}, more text here`;

function escapeHtml(strings, ...values) {
  let out = '';
  values.forEach(function(v, index) {
    let s = strings[index];
    out += s + values[index].replace('>', '>').replace('<', '<');
  });
  
  //add the last section
  out += strings[strings.length - 1];
  
  return out;
}
//output: someone being sneaky <script>alert(1)</script>, more text here

//You can even access the raw 'uncooked' string defined in the code
//if you wanna manipulate it before outputing somewhere. Useful for
//preserving the escape sequences and passing to other services expecting
//escaped input like Regexes for examples.
function displayRaw(strings, ...values) {
  console.log(strings.raw);
}
let exclamationPoint = '!';
displayRaw`This string will not be\n escaped or processed ${exclamationPoint}`;

//console output: ["This string will not be\\n escaped or processed ",""]

Promises

Until now Promises have been a third party lib or polyfill at best. ES6 finally brings A+ Promises as a first class object.

let p = new Promise(function(resolve, reject) {
  setTimeout(function() {
    resolve('some crazy data'); //or reject()
  });
});

p.then(function(data) {
  console.log(data);
  return Promise.resolve('Some more data');
})
.then(function(evenMoreData) {
  console.log(evenMoreData);

  throw new Error('insane error');
})
.catch(function(err) {
  console.log(err);
});

Promise.all([Promise().resolve(), Promise.resolve()])
  .then(function() {
    console.log('We\'re done processing');
  });

var fast = new Promise(function(resolve, reject) { setTimeout(resolve.bind(resolve, 'fast'), 100); });
var slow = new Promise(function(resolve, reject) { setTimeout(resolve.bind(resolve, 'slow'), 200); });
Promise.race([fast, slow]).then(function(speed) { console.log(speed); })

Iterators

class Range {
  constructor(start, end) {
    this.start = start;
    this.end = end;

    this.current = this.start;
    this.done = false;
  }

  [Symbol.iterator]() {
    let self = this;
    return {
      next() {
        self.current === self.end ?
          self.done = true :
          ++self.current;

        return {done: self.done, value: self.current};
      }
    }
  }
}

let range = new Range(0, 10);

for (let i of range) {
  console.log(i);
}

ES6 finally gives a first class iteration mechanism in the form of the Iterator protocol.

Let's face it, prototypal inheritance, um, to put it softly, sucks. JS makes this pattern even harder by being really verbose about it.

To top everything off, there's like 15 different way to implement it, some native, most aren't. ES6 attempts to fix some of this with lots of sugar to make the pill easier to swallow. 

Classes and Inheritance

//What we're replacing

function Parent(name) {
  this.name = name || 'Parent';
}

Parent.prototype.sayHi = function() {
  console.log(this.name); 
};

Parent.prototype.whoAmI = function(type) {
 console.log('I am the ' + type); 
}

function Child(name) {
  Parent.call(this, name); 
}

Child.prototype = Object.create(Parent.prototype, {
  whoAmI: {
    value: function(type) {
      Parent.prototype.whoAmI.call(this, type);
    }
  }
});
Child.prototype.constructor = Child;

let c = new Child('Charly');

c.sayHi();
c.whoAmI('Child');
class Papa {
    constructor(name = 'Big Daddy') {
        this.name = name;
    }
    
    sayHi() {
      console.log(`Hi, my name is ${this.name}`);
    }
    
    whoAmI() {
      console.log(`I am the parent`);
    }
}

class Babby extends Papa{
  constructor() {
    super();
    
    this.name = 'Little Timmy';
  }
  
  sayHi() {
    //Compare this:
    //Papa.prototype.sayHi.call(this, arguments);
    //vs, this:
    super.sayHi();
  }
}
var boy = new Babby();
console.log(boy.sayHi());
console.log(boy.whoAmI());

var f = boy.sayHi;
f.call(boy);

WebAssembly & ASM.js

What's all this about?

As some of you may have noticed, Javascript is not exactly known for speed. It's an amazing language, highly flexible, but fast? Definitely not.

As JS got more ubiquitous, browser and runtime vendors began to look for ways to make JS run faster. Projects like V8, TurboFan, Ion Monkey, Chakra and others are all born of this need. So what is the problem? The issue is that JS's flexibility comes at a cost. That cost is the JIT compiler doing a lot of the heavy lifting for the programmer, as opposed to languages like C where those concerns are managed by the programmer and are historically highly error prone. 

One of greatest but also slowest features of JS is it's runtime inference of types. We stand to get massive improvements in performance if only we could do a lot of pesky inference work at 

compile time instead of at runtime.

Enter ASM.JS and WebAssembly

One of the first steps taken in the direction of producing a faster JS was ASM.js. You can think of it as a proof-of-concept that it's even possible to make JS faster without fundamentally changing the language we know and love. According to Dr. Rauchmayer, compiling C++ to asm.js yielded 70% of native speeds, which is monstrously fast for some good ol' JS code.

 

So what's this WebAssembly? Glad you asked. Whereas asm.js is an optimized textual strict subset of JS, WebAssembly is a binary format for pretty much the same thing, a strict subset. The point is for WebAssembly to be a compilation target, something you can use as a target for performance critical code for things like games and multimedia and have some predictable performance at the end of the day.

Conclusion:

Start migrating today, because it's no longer too soon!

ES6: Diving Deeper

By signupskm

ES6: Diving Deeper

  • 2,091