Use ECMAScript 6 Today

Today, ECMAScript 6 is in the process of being finalized. ECMAScript is the foundation of JavaScript and, hence, exploring the proposed features today also means that we get a sneak peak at how we will be writing JavaScript in the near future! In this article, we’ll explore ten new features, with a significant focus on tools, browsers and transpilers.


A Brief History: ECMA, ECMAScript and JavaScript

JavaScript was originally developed by Brendan Eich of Netscape, and officially released as part ofNetscape Navigator 2.0 in 1995. A year later, JavaScript was submitted to ECMA International, a body that facilitates the standardization of information and communication technology and consumer electronics, so that it can be formalized industry-wise. ECMAScript, thus, became the name of the scripting language standardized in ECMA-262.

The ECMAScript standard forms the backbone of many other derived languages, including ActionScript andJScript. Through the years, ECMAScript has gone through four versions, with the discussions today very much revolving around version six, which has also been code-named, ECMAScript Harmony.

Version correspondence

Before we dive into these new features, it’s important to note that the ECMAScript standard forms the foundation of JavaScript. There are numerical differences between each of the JavaScript versions and the corresponding ECMAScript editions. This is to say that JavaScript is compatible with the ECMAScript standard, while providing more features. The table below summarizes the relationship between JavaScript and ECMAScript:

JavaScript Version ECMAScript Edition Year
JavaScript 1.1 ECMAScript edition 1 1997
JavaScript 1.5 ECMAScript edition 3 1999
JavaScript 2.0 ECMAScript Harmony Work in progress

ES6 Overview

Goals

JavaScript has come a long way since its humble beginnings nearly twenty years ago. Today, developers are writing thousands of lines of code creating complex JavaScript applications. Before we dive into the detailed features of ES6, you may want to look at the big picture that is defined in the specification drafts, in terms of requirements, goals, means and themes. One of the goals for ES6 is to be a better language for creating:

  • complex applications
  • libraries
  • code generators

Compatibility

The ES6 compatibility table is very useful, as it tells us the ES6 features that are supported in the current browser. It also gives us a handy link to the specifications for each of the features listed. Do note that some of the features’ existence might not mean full compliance with specifications. When working with Chrome, be sure to enable the “Experimental JavaScript” flags.

Features

Now that the big picture is defined, let’s explore how we can implement them. In the following sections, we will discuss ten features of ES6, using various tools so that we can understand ES6 both in theory and practice. Prior knowledge of JavaScript is a pre-requisite, so feel free to check out many resources on JavaScript.

Listed below are the features that we’ll go through with a different tool. Try them out one by one, or jump to the specific feature that you’d like to explore:

  1. Block scoping with let [ using Firefox browser ]
  2. Block scoping with const [ using Chrome browser ]
  3. Classes [ using Traceur ]
  4. Default function parameters [ using TypeScript ]
  5. Collections [ using NodeJS ]
  6. Destructuring [ using Firefox browser ]
  7. Rest parameters & Spread operator [ using Grunt plugin Traceur ]
  8. Iterators [ using Firefox browser ]
  9. Array comprehension [ using Firefox browser ]
  10. Modules (using ES6 Module Transpiler)

Feature 1 - Block Scoping with let

  • Documentation: let
  • Tool: Firefox browser 20.0: Menu > Tools > Web developer > Web Console

JavaScript variables are function-scoped. This means that, even if there are variables declared in a nested block, they are available throughout the function. Let’s review a short example below; we’ll simply use the web console in Firefox or Chrome to run them. What do you think will be the value of jsFuture?

1
2
3
4
5
var jsFuture = "es6" ;
( function () {
   if (!jsFuture) { var jsFuture = "es5" ; }
   console.log(jsFuture);
}());

In the above example, the value of jsFuture in the console.log statement will be “es5″. Crucial to your understanding is the fact that, in JavaScript, variable declarations are hoisted to the top, but variable initializations, on the other hand, are not. Hence, regardless of where the variables are initialized and declared, within the function scope, they will always be hoisted. The snippet below is exactly the same – with comments to illustrate this feature of variable hoisting.

1
2
3
4
5
6
7
var jsFuture = "es6" ;
( function () {
   // var jsFuture = undefined;
   // variable hoisting
   if (!jsFuture) { var jsFuture = "es5" ; }
   console.log(jsFuture); // "es5"
}());

ES6 tackles this issue with let, which is like var, except for the fact that it is block scoped instead of function scoped. Let’s consider another example with var below. Calling the function es[6]() will give us the value of i = 10. Notice that, even though var i = 0; is declared in the for loop, the scope of var idefaults to global. Hence, when the function es[6]() is executed, the value of i is 10.

1
2
3
4
5
6
7
var es = [];
for ( var i = 0; i < 10; i++) {
   es[i] = function () {
     console.log( "Upcoming edition of ECMAScript is ES" + i);
   };
}
es[6](); // Upcoming edition of ECMAScript is ES10

Let’s now use let. To code this out, we’ll use Firefox and open up the web console through the menu (Tools > Web developer > Web Console). Creating a block-scoped variable within the for loop, let c = i; made it block scoped.

1
2
3
4
5
6
7
8
var es = [];
for ( var i = 0; i < 10; i++) {
   let c = i;
   es[i] = function () {
     console.log( "Upcoming edition of ECMAScript is ES" + c);
   };
}
es[6](); // Upcoming edition of ECMAScript is ES6

Firefox already supports many upcoming ES6 features. Refer to the compliance table for Firefox to keep updated on which features are supported, and which ones are also compliant with the current specification.


Feature 2 - Block Scoping with const

  • Documentation: const
  • Tool: Chrome Browser > View > Developer > JavaScript Console

Constant definitions are now possible with constlet and const behave similarly in the sense that both are block scoped, but with const, the values are read-only and cannot be re-declared later on. Let’s review a simple code example in Chrome:


Feature 3 - Classes

  • Documentation: class
  • Tool: Traceur with Chrome Browser > View > Developer > JavaScript Console

In object-oriented programming languages, a class is a representation of an object. It forms the blueprint, while an object is an instance of a class. With regard to JavaScript, it is a class-less programming language and everything is an object. Traditionally, we’ve used functions and prototypes to implement classes. Let’s explore one common way of implementing class in ES5.

1
2
3
4
5
6
7
8
9
var Language = function (config) {
   this .name = config.name;
   this .founder = config.founder;
   this .year = config.year;
};
 
Language.prototype.summary = function () {
   return this .name + " was created by " + this .founder + " in " + this .year;
};

Next, let’s see how ES6 implements classes with minimal class declaration syntax that can be extremely important to distinguish classes and functions. To code out class using the ES6 syntax, we will use Google’s Traceur, which is a transpiler that compiles ES6 code into ES5. First, let’s create the html file structure within which we will insert the ES6 syntax for classes. In order to compile the Traceur code, we require both traceur.js to compile Traceur to JavaScript, as well as bootstrap.js to bind them all. Finally, Traceur will look for script type="text/traceur" tags to compile the relevant code inside the tags into vanilla JavaScript.

1
2
3
4
5
6
7
8
9
10
11
12
13
< html >
< head >
   < title >ES6 Classes title >
   < script src = "https://traceur-compiler.googlecode.com/git/bin/traceur.js" > script >
   < script src = "https://traceur-compiler.googlecode.com/git/src/bootstrap.js" > script >
head >
< body >
   < script type = "text/traceur" >
     // insert ES6 code
   script >
body >
html >

Next, within the script type="text/traceur" tags, let’s use the ES6 syntax to implement the same class that we previously did for Language.

1
2
3
4
5
6
7
8
9
10
class Language {
   constructor(name, founder, year) {
     this .name = name;
     this .founder = founder;
     this .year = year;
   }
   summary() {
     return this .name + " was created by " + this .founder + " in " + this .year;
   }
}

We can now create an instance of the class Language by opening the HTML file in the Chrome browser as var js = new Language. In the console, we’ll see the prompts for other properties of the language as well!

With such a clear syntax declaration, we can also move on to extend the class to implement a sub-classMetaLanguage that will inherit all the properties from the parent class Language. Inside the constructor function, we will require the function super that will call the constructor of the parent class so that it is able to inherit all of its properties. Lastly, we can also add on extra properties, such as version, as illustrated in the code below. Let’s review the ES6 syntax and run it in the Chrome browser:

1
2
3
4
5
6
class MetaLanguage extends Language {
   constructor(x, y, z, version) {
     super (x, y, z);
     this .version = version;
   }
}

Traceur is a useful transpiler that allows us to code using the ES6 syntax, while doing the heavy-lifting for us to compile it back to the current JavaScript version. Do try out other ES6 features in Traceur as well!


Feature 4 - Default Function Parameters

  • Documentation: default function parameters
  • Tool: TypeScript 0.8.3

With default function parameters, we can always have function parameters as an option by setting some defaults. The syntax for this feature in ES6 is extremely intuitive. The default parameters are defined when the functions are defined. Let’s have a look at the ES6 syntax below in a new TypeScript file with an extension of *.ts.

1
2
3
function history(lang = "C" , year = 1972) {
   return lang + " was created around the year " + year;
}

Next, we will install TypeScript as an npm module and run the file .*ts and compile it to vanilla JavaScript. Here are the installation and then compilation commands in the command line:

1
2
3
4
$ npm install -g typescript
$ npm view typescript version
0.8.3
$ tsc 4-default-params.ts

The command above will create a vanilla JavaScript file, called 4-default-params.js, which can then be called from an HTML file. Here’s the simple HTML file that will call the external JavaScript file that is created by the TypeScript compiler:

1
2
3
4
5
6
7
8
9
10
< html lang = "en" >
< head >
   < meta charset = "UTF-8" >
   < title >ES6 Default Parameters title >
head >
< body >
   < script src = "4-default-params.js" > script >
body >
html >

Finally, we will open the HTML file in Chrome/Firefox and call the function history() two times, with and without the function parameters. Notice that not passing in any function parameters will fall back to the default parameters:

Do check out other TypeScript features, including class or go through a TypeScript tutorial for more in-depth usage.


Feature 5 - Collections

  • Documentation: Sets and Maps
  • Tool: NodeJS v0.10.4

ES6 offers new data structures previously not available in JavaScript. Before we jump into exploring two such data structure (Sets and Maps), let’s see how we can run ES6 syntax with NodeJS. Install NodeJS; from here on, we will work in the command line. Firstly, we will check the NodeJS version installed, and then check which options will enable ES6 features with the command node --v8-options | grep harmony.

1
2
3
4
5
6
7
8
9
10
$ node --version
v0.10.4
 
$ node --v8-options | grep harmony
--harmony_typeof ( enable harmony semantics for typeof)
--harmony_scoping ( enable harmony block scoping)
--harmony_modules ( enable harmony modules (implies block scoping))
--harmony_proxies ( enable harmony proxies)
--harmony_collections ( enable harmony collections (sets, maps, and weak maps))
--harmony ( enable all harmony features (except typeof))

Next, start the NodeJS repl and query which properties are available for Set and Maps. We will start the NodeJS repl with node --harmony to enable all ES6 features.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ node --harmony
> Object.getOwnPropertyNames(Set.prototype)
[ 'constructor' ,
   'add' ,
   'has' ,
   'delete' ]
> Object.getOwnPropertyNames(Map.prototype)
[ 'constructor' ,
   'get' ,
   'set' ,
   'has' ,
   'delete' ]
> . exit
$

Sets

Sets are simple data structures that are similar to arrays, but each value is unique. Let’s create a new file, called 5-sets.js, and insert some code to create, add, delete and query the new set that we will create. Also, note that we will add “Hippo” data twice, but in the set, it will be registered only once!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var engines = new Set(); // create new Set
 
engines.add( "Gecko" ); // add to Set
engines.add( "Trident" );
engines.add( "Webkit" );
engines.add( "Hippo" );
engines.add( "Hippo" ); // note that Hippo is added twice
 
console.log( "Browser engines include Gecko? " + engines.has( "Gecko" ));    // true
console.log( "Browser engines include Hippo? " + engines.has( "Hippo" ));    // true
console.log( "Browser engines include Indigo? " + engines.has( "Indigo" ));   // false
 
engines. delete ( "Hippo" ); // delete item
console.log( "Hippo is deleted. Browser engines include Hippo? " + engines.has( "Hippo" ));    // false

Run the file in the node repl with the command node --harmony 5-set.js. Note that, even though “Hippo” was added twice to the set, upon deleting it, the set didn’t include it anymore. This once again illustrates that a set is a data structure that can only contain unique values.

Maps

Maps are quite similar to JavaScript object key-value pairs. Using a unique key, we can retrieve the value. In ES6, the key can be any JavaScript data type and not just strings. That’s the interesting part! Let’s create a new file, called 5-map.js, to try out the create, get and delete features:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var es6 = new Map(); // create new Map
 
es6.set( "edition" , 6);        // key is string
es6.set(262, "standard" );     // key is number
es6.set(undefined, "nah" );    // key is undefined
 
var hello = function () {console.log( "hello" );};
es6.set(hello, "Hello ES6!" ); // key is function
 
console.log( "Value of 'edition' exits? " + es6.has( "edition" ) );     // true
console.log( "Value of 'year' exits? " + es6.has( "years" ) );          // false
console.log( "Value of 262 exits? " + es6.has(262) );                 // true
console.log( "Value of undefined exits? " + es6.has(undefined) );     // true
console.log( "Value of hello() exits? " + es6.has(hello) );           // true
 
es6. delete (undefined); // delete map
console.log( "Value of undefined exits? " + es6.has(undefined) );      // false
 
console.log( es6.get(hello) ); // Hello ES6!
console.log( "Work is in progress for ES" + es6.get( "edition" ) ); // Work is in progress for ES6

As shown with the ES6 collections features, NodeJS harmony option already supports others ES6 features such as block scoping, proxies and modules. Do try them out in NodeJS as well!


Feature 6 - Destructuring

  • Documentation: Destructuring
  • Tool: Firefox browser 20.0: Menu > Tools > Web developer > Web Console

In programming languages, the term “destructuring” denotes pattern matching. In ES6, we can do some pretty nifty pattern matching in arrays and objects that previously would have taken us more than one step. Let’s explore some of them by coding it out in Firefox web console.

Array destructuring

With array destructing, we can initialize variables at once, or even swap them instead of having the conventional way of creating a var temp; temporary variable.

1
2
3
4
5
var [ start, end ] = [ "earth" , "moon" ] // initialize
console.log(start + " calling " + end); // earth calling moon
 
[start, end] = [end, start] // variable swapping
console.log(start + " calling " + end); // moon calling earth

Destructuring also becomes a useful shorthand when returning multiple values from a function, as we do not need to wrap around an object anymore. Also, to skip certain variables, just leave the array element empty:

1
2
3
4
5
function equinox() {
   return [20, "March" , 2013, 11, 02];
}
var [date, month, , ,] = equinox();
console.log( "This year's equinox was on " + date + month); // This year's equinox was on 20March

Object destructuring

Due to destructuring, variables can also be initialized from an object that is returned from a function even with deeply nested objects. Also, just like the array patterns, we can skip the ones not needed. Here’s the snippet of code that illustrates just this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function equinox2() {
   return {
     date: 20,
     month: "March" ,
     year: 2013,
     time: {
       hour: 11, // nested
       minute: 2
     }
   };
}
 
var { date: d, month: m, time : { hour: h} } = equinox2();
// h has the value of the nested property while "year" and "minute" are skipped totally
 
console.log( "This year's equinox was on " + d + m + " at " + h); // This year's equinox was on 20March at 11

Feature 7 - Rest Parameters and Spread Operators

  • Documentation: Rest parameters & Spread operator
  • Tool: Grunt plugin Traceur

Rest parameters

In ES6, rest parameters allows us to easily use a few fixed parameters in a function, along with the rest of the trailing and variable number of parameters. We already use arguments, which is an array-like object that defines the arguments passed to a function, but clearly we cannot use the array function to manipulate these arguments. With a clear syntax in ES6, it also moves the intent of the developer into the syntax level with three dots ... to denote a variable number of arguments.

Let’s try to use rest parameters in the ES6 syntax with gruntjs and its plugin for the traceur transpiler, which we used in the previous section.

  1. Install grunt command line utility:

    1
    2
    $ npm uninstall -g grunt
    $ npm install -g grunt-cli
  2. Create a file, called package.json, which will define the various modules needed to run Grunt. Note that this list of dependencies includes the traceur plugin:

    1
    2
    3
    4
    5
    6
    7
    8
    {
       "name" : "rest-params" ,
       "version" : "0.1.0" ,
       "devDependencies" : {
         "grunt" : "0.4.1" ,
         "grunt-traceur" : "0.0.1"
       }
    }
  3. Create the Gruntfile.js which will contain just one task traceur that will convert ES6 syntax to today’s JavaScript. With this, we will be able to try out ES6 rest parameters.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    module.exports = function (grunt) {
     
       grunt.initConfig({
         pkg: grunt.file.readJSON( 'package.json' ),
         traceur: {
           custom: {
             files:{
             'js/' : [ 'rest-spread.js' // dest : 1
             }
           }
         }
       });
     
       grunt.loadNpmTasks( 'grunt-traceur' );
       grunt.registerTask( 'default' , [ 'traceur' ]);
     
    };
  4. Create a simple index.html to call the traceur-compiled JavaScript file, js/rest-spread.js:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    < html >
    < head >
       < title >ES6 Rest parameters title >
    head >
    < body >
       < script src = "js/rest-spread.js" > script >
    body >
    html >
  5. Most importantly, we’ll create the file rest-spread.js, which will contain the rest parameter syntax:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    function push(array, ...items) { // defining rest parameters with 3 dot syntax
       items.forEach( function (item) {
         array.push(item);
         console.log( item );
       });
    }
     
    // 1 fixed + 4 variable parameters
    var planets = [];
    console.log( "Inner planets of our Solar system are: " );
    push(planets, "Mercury" , "Venus" , "Earth" , "Mars" ); // rest parameters
  6. Finally, we will run grunt in the command line, which will, by default, run the traceur task and create the file, js/5-rest-spread.js. Next, just view the file index.html in the browser console:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    $ npm install
    $ grunt
    ╰─$ grunt
    Running "traceur:custom" (traceur) task
    js/ [ 'rest-spread.js' ]
    Compiling... js/
    Compilation successful - js/
    Writing... js/
    js /rest-spread .js successful.
    Writing successful - [object Object]

Spread operator

A spread operator is the opposite of rest parameters. When calling a function, we can pass in the fixed argument that is needed along with an array of a variable size with the familiar three dot syntax, to indicate the variable number of arguments.

We will use the same project as the rest parameters above and append in the spread operator code to the file rest-spread.js. In the example below, the function requires six separate arguments. When calling the function, the data is passed as an array with the spread operator. Let’s see how the syntax looks, when calling the function with fixed arguments as well as a variable number of arguments:

  1. Append the spread operator code to rest-spread.js:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    // Spread operator "...weblink"
    function createURL (comment, path, protocol, subdomain, domain, tld) {
           var shoutout = comment
             + ": "
             + protocol
             + "://"
             + subdomain
             + "."
             + domain
             + "."
             + tld
             + "/"
             + path;
     
       console.log( shoutout );
    }
     
    var weblink = [ "hypertext/WWW/TheProject.html" , "http" , "info" , "cern" , "ch" ],
       comment = "World's first Website" ;
     
    createURL(comment, ...weblink ); // spread operator
  2. Run the traceur compile through the Grunt task in the command line, and view the file, index.html, in the browser:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    $ grunt
    Running "traceur:custom" (traceur) task
    js/ [ 'rest-spread.js' ]
    Compiling... js/
    Compilation successful - js/
    Writing... js/
    js /rest-spread .js successful.
    Writing successful - [object Object]
     
    Done, without errors.

If you’re already using GruntJS as a build tool in your current project, it will be easy to integrate it with ES6 plugins. So do try out other GruntJS ES6-related plugins to compile ES6 syntax to current JavaScript.


Feature 8 - Iterators

  • Documentation: Iterator
  • Tool: Firefox browser 20.0: Menu > Tools > Web developer > Web Console

JavaScript offers for-in for iteration, but it has some limitations. For example, in an array iteration, the results with a for-in loop will give us the indexes and not the values. Let’s take a look at the code below to illustrate this:

1
2
3
4
5
6
7
8
9
10
11
12
13
var planets = [ "Mercury" , "Venus" , "Earth" , "Mars" ];
for (p in planets) {
   console.log(p); // 0,1,2,3
}
 
var es6 = {

你可能感兴趣的:(Use ECMAScript 6 Today)