ES6 in io.js

io.js - the famous Node.js fork recently put out their initial release touting the slogan "Bringing ES6 to the Node Community!". io.js got these features ahead of Node.js by aggressively following the latest versions of the V8 JavaScript engine. As an outsider looking in, I took a few hours to poke around, and shall report my findings here.

Installation

Binary distributions of io.js are available from their front page, and you can download a binary for Linux, Windows, Mac, or build it from source. However, the binary installers will overwrite the node and npm executables on your system if you have Node.js installed. Therefore, I recommend using nvm to install io.js in a conflict-free way. Installing nvm is quite easy if you haven't done it before. If and when you have nvm, simply do

$ nvm install io.js ######################################################################## 100.0% WARNING: checksums are currently disabled for io.js Now using io.js v1.0.3

Check that it worked:

$ iojs >

Voilà! Note that node is aliased to iojs , and npm is still called npm .

ES6 Features Overview

Although some folks have already been using ES6 for a while via transpilers, when I work with transpiled code, it feels like I am having to simultaneously debug two versions of the code - debugging is hard enough with just one version. For this reason, having native support makes it much more appealing for me.

The io.js ES6 page gives information about the changes they've made to the ES6 support in the engine. They have done away with the --harmony flag - which in Node 0.11+ you had to include if you wanted to use any ES6 features at all. In io.js, you get them right out of the box! The current list of ES6 features enabled by default are:

let statement

statement const statement

statement Map and Set

and WeakMap and WeakSet

and Generators

Binary and Octal literals

Promises

Some additional string methods

Symbols

Template strings

They also added the --es_staging flag which would allow you to gain access to features that are done but haven't been well tested yet. For features that are in progress of being implemented, you'd have to gain access to each feature individually by using the harmony flag corresponding to it. You can get the list of harmony feature flags via:

$ iojs --v8-options|grep "harmony" --es_staging (enable all completed harmony features) --harmony (enable all completed harmony features) --harmony_shipping (enable all shipped harmony fetaures) --harmony_modules (enable "harmony modules (implies block scoping)" (in progress)) --harmony_arrays (enable "harmony array methods" (in progress)) --harmony_array_includes (enable "harmony Array.prototype.includes" (in progress)) --harmony_regexps (enable "harmony regular expression extensions" (in progress)) --harmony_arrow_functions (enable "harmony arrow functions" (in progress)) --harmony_proxies (enable "harmony proxies" (in progress)) --harmony_sloppy (enable "harmony features in sloppy mode" (in progress)) --harmony_unicode (enable "harmony unicode escapes" (in progress)) --harmony_tostring (enable "harmony toString") --harmony_numeric_literals (enable "harmony numeric literals") --harmony_strings (enable "harmony string methods") --harmony_scoping (enable "harmony block scoping") --harmony_classes (enable "harmony classes (implies block scoping & object literal extension)") --harmony_object_literals (enable "harmony object literal extensions") --harmony_templates (enable "harmony template literals")

Now, let's drill down to the individual features.

let and const

The let and const statements are only available in strict mode. So put "use strict" at the top of each JS file where you wish to use them.

The let statement is a replacement for the var statement which has lexical scoping. What this means is that while a variable defined with var is visible to the function within which it is declared, let is visible only to the code block within which it is declared. In JavaScript, a code block is a compound statement enclosed in { and } that contains zero or more statements. You commonly use code blocks within if-statements, for loops, while loops, and as the body of a function definition. But, it's also possible to write a standalone code block.

Here is an example of let :

"use strict" if (player.partner){ let partner = player.partner // do stuff with partner here } console.log(parter) // this throws partner is not defined

Here is let in a for loop:

"use strict" for (let i = 0; i < 10; i++){ console.log(i) } console.log(i) // this throws i is not defined

const is like let except that once declared, the variable cannot be reassigned to another value.

"use strict" const ITERATIONS_TO_RUN = 10 ITERATIONS_TO_RUN = 12 // throws TypeError: Assignment to constant variable.

Map and Set

ES6 has introduced the Map and Set data structures for your convinience. Now you might be wondering, why do we even need a map? What's wrong with using object literals as maps? Well, it's been argued that an object is not a hash (or er map). The short version is that an object inherits all of Object.prototype 's properties, which in most cases is unwanted if you want to use it as a map.

Now, here's an example of using Map :

> var m = new Map undefined > m.set('name', 'Bobby') {} > m.get('name') Bobby > m.size 1 > m.set('age', 5) {} > m.has('age') true > m.has('foobar') false > m.forEach(function(value, key){ console.log(key + ' maps to ' + value) }) name maps to Bobby age maps to 5 > m.get('hasOwnProperty') // avoids the `hasOwnProperty` trap undefined > m.clear() undefined > m.size 0

And here is Set in action:

> var s = new Set undefined > s.add(1) {} > s.size 1 > s.add(2) {} > s.size 2 > s.add(1) // adding a duplicate here {} > s.size // no change in size 2 > s.has(1) true > s.has(2) true > s.has(3) false > s.forEach(function(n){ console.log('Set has ' + n) }) Set has 1 Set has 2

WeakMap and WeakSet

WeakMap and WeakSet are new data types that mirror Map and Set , but unlike Map and Set - which can be implemented as polyfills - these can only be implemented natively. The word "weak" refers to weak references. A weak reference is an object reference that is ignored by the garbage collector. If there exists only weak references - no more strong references - pointing to the object in question, then that object can be destroyed and its memory relinquished.

Let's talk about WeakSet first - because it is easier to explain. A WeakSet 's API is a subset of Set 's. However, you cannot store primitive values in it:

> var ws = new WeakSet undefined > ws.add(1) TypeError: Invalid value used in weak set

This makes sense because primitive values are stored by value, not by reference, and it would make no sense to even speak of weak references. So, you'll need to put objects in it instead:

> var bob = {name: 'Bob'} undefined > var jen = {name: 'Jen'} undefined > ws.add(bob) {} > ws.add(jen) {} > ws.has(bob) true > ws.has(jen) true > var jim = {name: 'Jim'} undefined > ws.has(jim) false > ws.delete(jen) true > ws.has(jen) false

WeakSet has no size property, or a way of iterating its members

> ws.size undefined > ws.forEach(function(item){ console.log('WeakSet has ' + item)}) TypeError: undefined is not a function > ws.forEach undefined

This is precisely because the references are weak, and as such, the objects could be destroyed without notice, at which point it would be impossible to access them anymore. One possible use of WeakSet is to store a set of related DOM elements without worry of memory leaks when the elements are removed from the document.

A WeakMap is like Map except that all of its keys are weak references. They also must not be primitive values.

var wm = new WeakMap > var person = {name: 'Bob'} undefined > var creditCard = {type: 'AMEX', number: 123456789} undefined > wm.set(person, creditCard) {} > wm.get(person) { type: 'AMEX', number: 123456789 }

As with Set, there is no way to get the size of the WeakMap or iterate over it's keys or values:

> wm.size undefined > wm.forEach undefined

When the application ceases to hold a strong reference to person , its entry in wm could be destroyed, and creditCard could in turned be destroyed as well. Read more about WeakMap and WeakSet.

for-of

In addition to the classic for-in statement, ES6 has added the for-of statement which allows you to succiently iterate the values of arrays, iterables, and generators. The latter two to be discussed below.

Here is for-of iterating over an array:

> var arr = [1, 2, 3] undefined > for (var n of arr) console.log(n) 1 2 3

Iterables and Iterators

So, you can also use the for-of statement to iterate over iterables.

But what is an iterable?

An iterable is an object that has an associated method which initializes and returns an iterator. The way you associate this method with an object is:

var myObj = {} myObj[Symbol.iterator] = function(){ // I'll cover symbols later return new MyIterator }

But what is an iterator?

An iterator is an object that adheres to the iterator protocol - which requires simply one method:

next() - which advances to the next item in the sequence each time it is called and returns an object that contains two properties

- which advances to the next item in the sequence each time it is called and returns an object that contains two properties done - a boolean which is true if and only if the sequence has already ended

- a boolean which is true if and only if the sequence has already ended value - the current value in the sequence

As an example, below is how I've managed to make a simple custom link list implementation iterable:

function LLNode(value){ this.value = value this.next = null } LLNode.prototype[Symbol.iterator] = function(){ var iterator = { next: next } var current = this function next(){ if (current){ var value = current.value var done = current == null current = current.next return { done: done, value: value } }else{ return { done: true } } } return iterator } var one = new LLNode(1) var two = new LLNode(2) var three = new LLNode(3) one.next = two two.next = three for (var i of one){ console.log(i) }

The output of this program is

1 2 3

Generators

Generators allow you to write an iterable in a succient and easily understandable manner. It also allows you to represent infinite sequences.

Here is how I could write a generator that iterates all integers starting from 0:

function *naturalNumbers(){ var n = 0 while (true){ yield n++ } }

Note the function * syntax and the yield statement - these indicate that this is a generator function rather than a normal function. When you call a generator function, you get back a generator, which implements the iterator protocol:

> var gen = naturalNumbers() {} > gen.next() { value: 0, done: false } > gen.next() { value: 1, done: false }

It's also an iterable! You can verify this: if you call its iterator method, you get back the generator itself:

> gen[Symbol.iterator]() === gen true

But the more succient way to iterate over an iterable, of course, is via the for-of statement:

for (var n of naturalNumbers()){ console.log(n) }

Oops! Infinite loop (facepalm).

Generators are also cool because it is one solution (among several) to the callback hell problem. Notably, co and koa are frameworks which make heavy use of generators, and they both work in io.js out of the box. Read more for more in-depth treatments of generators.

Binary and Octal Numbers

Binary numbers are prefixed with 0b , and octal numbers are prefixed with 0O - that is, "zero" "O".

console.log(0b100) console.log(0O100)

The above program outputs:

4 64

Promises

The development of promises was very much a grassroots effort, starting out as libraries or components within various frameworks. Today, there are estabilished libraries like RSVP, Q, and Bluebird. Most of the major frameworks have promises built-in. There is a standard for promises called Promises A+ which most of the major implementations adhere to. To top it off, promises have been brought into the runtime itself! The story behind promises is quite inspiring.

Below is an example of how to turn a callback-based http client library into a function that returns a promise:

var request = require('superagent') fetch('http://iojs.org') .then(function(reply){ console.log('Returned ' + reply.text.length + ' bytes.') }) function fetch(url){ return new Promise(function(resolve, reject){ request(url).end(function(err, reply){ if (err){ reject(err) }else{ resolve(reply) } }) }) }

Promises can also be used effectively with generators - which is the strategy employed by co. Read this tutorial for a more in-depth explanation of promises.

New String Methods

Some new methods have been added to the native String object.

String.fromCodePoint(number) and .codePointAt(idx) are like String.fromCharCode and .charCodeAt(idx) except that they support unicode and therefore high code points translate into multi-byte characters > s = String.fromCodePoint(194564) 'ð¯ „' > s.codePointAt(0) 194564

startsWith(s) and endsWith(s) > 'Hello, world!'.startsWith('Hello') true > 'Hello, world!'.endsWith('!') true

repeat(n) > 'foobar'.repeat(5) 'foobarfoobarfoobarfoobarfoobar'

normalize() - returns the unicode normalization form of the string. To actually understand what that means, read about unicode equivalence.

Symbols

The name symbol could be confusing because these symbols are not like the ones in Ruby or Smalltalk. Symbols in ES6 are used as hidden object properties. If you are a Pythonista: think double underscore magic methods.

var secretMethod = Symbol('secret') var obj = {} obj[secretMethod] = function(){ return 'foobar' } obj[secretMethod]() // returns `foobar`

Now, secretMethod won't show up within a for-in loop through the object's properties. In fact, no string property corresponds to the symbol referenced by secretMethod and there is no way to access the method without having a reference to the symbol. There are global "well-known" symbols in the system such as Symbol.iterator - which we've seen used to associate an object with its iterator. By all means, read more about symbols.

Template Strings and Multi-line Strings

Template strings are borrowed from Ruby and Perl's string interpolation. It saves developers from having to awkwardly add together bits of strings - which often results in lots of quotes.

> var name = 'Bobby' undefined > `Hello, ${name}!` 'Hello, Bobby!'

Note that template strings are enclosed by upticks "`" rather than single or double quotes - you'll have to reach up with your left pinky. What's exciting to me is that you can now write multi-line strings:

var age = 5 var sql = ` select name from people where age > ${age}; `

Template strings have one more feature - to allow a custom function to evaluate the template in question. This is useful for situations which require specific parameter escaping - such as when sanitizing SQL parameters to prevent SQL injection attacks.

var age = 5 var sql = sqlSanitize` select name from people where age > ${age}; `

You can read more for in-depth treatments of template strings.

Notable Features Behind Flags

Some of the notable features still marked as in progress in io.js - version 1.0.3 at the time of this writing - are:

Overall Impression

I feel optimistic about the state of ES6 features on io.js. I like that all of these features work out of the box without any special flags. Mentally, this designation makes these features legit. For the most part, when these features are used the wrong way, the thrown error messages are helpful in guiding users. The features I am most excited about are generators and template strings. If I were starting a new hobby project today I would definitely give io.js a try, have a play, go wild, and try out these features in the wild.