ES6: Javascript in Harmony: An Overview of New Language Features

ECMAScript 6 brings powerful new capabilities and some tasty syntactical sugar to the ubiquitous Javascript language, as it continues to grab an ever increasing slice of developer mindshare.

Now that the ES6 feature set is frozen and just minor refinements will be made over the coming months, much of this new functionality has already landed in modern browsers, and will continue to roll out with each new browser update.

For the impatient, there are also now several excellent precompile / polyfill / shim tools available, so you can start using these new features immediately; safely knowing that your code will soon run ‘natively’ on every Javascript platform.

Javascript has become far more successful and ubiquitous than Brendan Eich or Netscape ever imagined, and several of the significant changes in the new spec, are designed with server side scripting platforms in mind (eg Node.js, and various scriptable database engines…).

This is an overview of some of the key changes in ES6

ES6 Javascript in Harmony

Block Bindings

A new let keyword declares a variable (like var) but restricts its scope to the current code block (unlike var which is only scoped to function block); let also does not share var hoisting characteristics, so a let variable cannot be used until after its declaration (or it will throw an error).

function foo(){
  let x = 123;
  if(true){
    let x = 456;      // different variable
    console.log(x);  // 456
  }
  console.log(x);    // 123
}

var i=0;
for(let i=i; i<10; i++){
  console.log(i);   // 0 1 2 3 4 5 6 7 8 9
}
console.log(i)      // 0

const is similar to let except once initialized, cannot be changed. const declarations share the same block scoping as let.

    const PI = 3.14159265359;
    let circumference = 2 * PI * r;

Arrow functions

Syntactical sugar (mostly) to facilitate simple function declaration, which is particularly handy for anonymous functions.

    // these are equivalent
    var sq = x => x * x
    var sq = function(x){ return x * x }

However, there are some important differences with standard function declarations:

  • The value of this inside an Arrow function is always determined by where it is defined, not where it is used (avoiding common “var self = this” shuffle)
  • Arrow functions cannot change this within the function
  • Arrow functions cannot be used with new as constructors
  • Arrow functions have no arguments object, so must use standard named or new rest parameters
    var sum = (a,b) => a + b;
    var getName = () => "Pablo"; // empty parenthesis needed when no params
    var foo = (a,b) => { 
      let c = a + b; 
      console.log(a,b,c);        // 1 2 3
    }

Because curly braces are used for the function’s body, to return an object literal (outside of a function body) you must wrap the literal in parentheses.

    var getObj = id => ({ id:id, name:"ObjA" });

Destructured assignment

Several variables can be initialized in a single statement and the following are equivalent:

    var [d, m, y] = [13, 2, 1963];
    var d = 13, m = 2, y = 1963;

    // useful for swapping
    [x, y] = [y, x]

    // or multiple return values
    function now(){ return [13, 2, 2013, 8, 0]; }
    var [d, m] = now(); // d = 13, m = 2
    var [,,y] = now(); // y = 2013

Destructuring also works with objects.

    function today() { return { d:13, m:2, y:1963 }; }
    var { m:month, y:year } = today(); // month = 2, year = 1963

    // shorthand
    var { m, y } = today(); // m = 2, y = 1963

Destructuring assignments can also work with nested elements.

Parameters

There are several new conveniences for working with parameters.

Default Parameters

You can now assign default values to function parameters, which will be used if the parameter is not formally passed.

    function myFunc( url, timeout=3000, callback=function(){} )

Rest Parameters

Designed to replace use of arguments when working with variable numbers of parameters, and indicated by three dots ... preceding a named parameter. The named parameter then becomes an array containing the rest of the parameters (passed but not specified).

    function sum(first, second, ...others){
      console.log(others.length);
    }

Spread Operator

Spread is the opposite of Rest parameters and splits an array into individual parameters.

    var a = [1,2,3,4,5]
    max = Math.max(...a) 
    // equivalent to Math.max.apply(null,a) or Math.max(1,2,3,4,5)

Spread can also be used in conjunction with other regular parameters.

Destructured Parameters

Similar to a destructured assignment (see above).

    function setCookie( name, val, { secure, path, domain, expires })

Iterators and Generators

Iterators are objects that have a next() method which returns a results object with two properties, value and done. Iterators offer a collection of values one at a time through successive calls to next() returning the appropriate value or result with done==true if no more values to read.

ES6 collection objects (like arrays, maps and sets) all have three default iterators. These are entries(), values() and keys() and cover most common iteration needs.

    var a=['one','two','three']
    for(let e of a.entries()) console.log(e)  // [0,'one']  [1,'two']  [2,'three']
    for(let v of a.values()) console.log(v)   // one  two  three
    for(let k of a.keys()) console.log(k)     // 0  1  2

for..of

ES6 also introduces a new syntax for dealing with iterators. The for-of loop which is used for looping over iterable objects.

    for(let i of ['one','two','three') console.log(i)

NOTE: for..of and for..in loops look similar, but are fundamentally different. for..in is for inspecting object properties and looping over the object’s enumerable properties, whereas for..of is for looping over iterators.

for..of is also available in array comprehensions.

    [ x for (x of a) if (x>5) ]

Generators

A generator is a special kind of function that returns an iterator by inserting a * after the function keyword. A new yield keyword is used inside of generators to specify values that iterator should return when next() is called.

Generators stop execution after each yield until next() is called again, which is a powerful new paradigm (eg for managing asynchronous processes).

    function* gen(){
      yield 1;
      yield 2;
      yield 3;
    }
    let it = gen();
    for(let i of it) console.log(i); // outputs 1 2 3

    let it2 = gen();
    it2.next() // { value:0, done:false }
    it2.next() // { value:1, done:false }
    it2.next() // { value:2, done:false }
    it2.next() // { value:undefined, done:true }

Generators also let us write lazy versions of many functions (eg. filter, map, reduce…) which are very efficient and elegant.

Collections

ES6 introduces some new collection structures.

Sets

Sets are simple data structures similar to arrays where each value is unique.

    let s = new Set([1,2,3]);
    s.has(4);     // false
    s.add(4);     // [1,2,3,4]
    s.add(2);     // still [1,2,3,4]
    s.delete(3);  // [1,2,4]

Maps

Maps are similar to JavaScript object key-value pairs except the key can be any JavaScript data type, not just strings.

    m = new Map();
    o = {x:'blah'};
    m.set(o,'something to store');
    m.has(o); // true
    console.log(m.get(o));

Maps can also be used with iterators.

    for (let [k, v] of m) console.log(k, v);

Symbols

Symbols generate unique inaccessible keys, useful in maps and class private members.

    let a = Map();
    {
      let k = Symbol();
      a.set(k, 'value');
      // Here, we can get and reset 'value' as a.get(k).
    }
    // Here, a.get(k) is invalid, a.size is 1, but the key cannot be seen.

WeakMaps and WeakSets

WeakMap helps developers avoid memory leaks by allowing the garbage collector to dispose objects only referenced by WeakMap. WeakMap keys must be objects and are not able to enumerate their keys.

WeakSet only objects, which can be collected if there are no other references or mentions.

Template Strings

New String declaration syntax (using back-ticks “) that facilitates simple String literals with embedded expression evaluation.

    var name = 'Paul', age = 99;
    var s = `$(name), is apparently $(age) years old`

The syntax can also be used fro simple multi-line strings.

    var longString = `here we have
     a multiline string 
     using backtick quotes`;

Promises

Promises are a mechanism for handling results (and errors) from asynchronous operations. You can achieve the same thing with callbacks, but promises provide improved readability via method chaining and simple error handling. Promises are already used in many JavaScript libraries.

    getJSON("/api/product/1").then( function(p) {
      return getJSON(p.description);
    }).catch(function(err) {
      console.log('Oops..',err)
    });

References

Posted in Feature, Java, JavaScript, Tips & Tricks

Leave a Reply