Hemanth.HM

A Computer Polyglot, CLI + WEB ♥'r.

ES6 on node.js

| Comments

In my old post I wrote of the same topic using shims. But here I would like to demonstrate many ES6 (harmony) features using raw node.js

Let the code do the talking!

Node version : v0.11.6

Let's grep some harm ;) :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ node --v8-options | grep harm
  --harmony_typeof (enable harmony semantics for typeof)
  --harmony_scoping (enable harmony block scoping)
  --harmony_modules (enable harmony modules (implies block scoping))
  --harmony_symbols (enable harmony symbols (a.k.a. private names))
  --harmony_proxies (enable harmony proxies)
  --harmony_collections (enable harmony collections (sets, maps, and weak maps))
  --harmony_observation (enable harmony object observation (implies harmony collections)
  --harmony_typed_arrays (enable harmony typed arrays)
  --harmony_array_buffer (enable harmony array buffer)
  --harmony_generators (enable harmony generators)
  --harmony_iteration (enable harmony iteration (for-of))
  --harmony_numeric_literals (enable harmony numeric literals (0o77, 0b11))
  --harmony_strings (enable harmony string)
  --harmony_arrays (enable harmony arrays)
  --harmony (enable all harmony features (except typeof))

Kool, now let's enable all of them with some awk magic, along with strict mode!

1
$ node --use-strict $(node --v8-options | grep harm | awk '{print $1}' | xargs) #ES6

Let's get started!

BLOCK SCOPING :

The keyword let helps in defining variables scoped to a single block.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function aboutme(){
  {
    let gfs = 10;
    var wife = 1;
  }
  console.log(wife);
  console.log(gfs);
}

// Let's invoke aboutme
aboutme();

// Would result in :
1
ReferenceError: gfs is not defined.

gfs got a ReferenceEorror as it was in a block and declared with let, syntactically similar to var, but defines a variable in the current block. The above is a simple example, but let is more useful in creating closures in loops and works better than var.

GENERATORS :

Generators helps to build iterators, yield is not a keyword in ES6 so the syntax to declare a generator function is function* (){} Let's see an example :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function *Counter(){
var n = 0;
while(1<2) {
  yield n;
  ++n
  }
}

var CountIter = new Counter();

CountIter.next()
// Would result in { value: 0, done: false }

// Again 
CountIter.next()¬
//Would result in { value: 1, done: false }

The done attribute will be true once the generator has nothing more to yield. Interestingly a generator can also yield another generator! :)

PROXIES :

Proxies provide a meta-programming API, that helps the programmer to define primitive object behaviour using traps

1
2
3
4
5
6
7
8
9
var life = Proxy.create({
    get: function(obj,value){
        return value === "ans" ? 42 : "Meh! Nothing like : " + value
    }
});

life.ans // Would return 42

life.lol // Would return Meh! Nothing like lol.

The above can be extended to simulating __noSuchMethod__

Update 0 : (Better way from the specs draft)

1
2
3
4
5
6
7
8
9
10
11
12
Object.createHandled = function(proto, objDesc, noSuchMethod) {
  var handler = {
    get: function(rcvr, p) {
      return function() {
        var args = [].slice.call(arguments, 0);
        return noSuchMethod.call(this, p, args);
      };
    }
  };
  var p = Proxy.create(handler, proto);
  return Object.create(p, objDesc);
};

P.S : Note This API is superseded by the newer direct proxies API, but in node this is not yet there, as a result :

1
2
> Proxy()
TypeError: Property 'Proxy' of object #<Object> is not a function

We must wait till v8 implements it.

MODULES :

Modules helps in separation of code and increase modularity.

1
2
3
4
5
6
7
8
9
10
11
module Counter{
    var n = 0;
    export function inc() { return ++n; }
    export function dec() { return --n;}
    export function cur() { return n;}
}

Counter.n // undefined.
Counter.inc() // 1
Counter.dec() // 0
Counter.cur() // 0

Object.observe :

Object.observe provides a runtime capability to observe changes to an object.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
> var todos = ["eat","code","code","sleep"];

// Using Array.observe
> Array.observe(todos,function(changes) { console.log(changes); })
> todos.pop()
'sleep'
> [ { type: 'splice',
    object: [ 'eat', 'code', 'code' ],
    index: 3,
    removed: [ 'sleep' ],
    addedCount: 0 } ]
> todos.push("sleep")
4
> [ { type: 'splice',
    object: [ 'eat', 'code', 'code', 'sleep' ],
    index: 3,
    removed: [],
    addedCount: 1 } ]

// Similarly with Object.observe
> var obj = {}
> Object.observe(obj,function(changes) {console.log(changes); })
> obj.name = "hemanth";
'hemanth'
> [ { type: 'new', object: { name: 'hemanth' }, name: 'name' } ]

COLLECTIONS (Maps and Sets) :

Map objects are simple key/value maps, but it is different when compared to Object :

  • Objects have default key/value pairs from prototype.

  • Keys of an Object are Strings, where they can be any value for a Map.

  • Keeping track of size for an Object is manual, but Maps have size attribute.

Set objects let you store unique values of any type, whether primitive values or object references, but we still can't iterate them in node :(

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
> Object.getOwnPropertyNames(Set.prototype)
[ 'constructor', 'size', 'add', 'has', 'delete', 'clear' ]

> Object.getOwnPropertyNames(Map.prototype)
[ 'constructor', 'size', 'get', 'set', 'has', 'delete', 'clear' ]

var myMap = new Map();
myMap.set(NaN, "not a number");
myMap.get(NaN); // "not a number"


var mySet = new Set();
var todos = ["eat","code","sleep","code","drink","code"]
todos.forEach(function(t) { mySet.add(t); } )
todos.length // 6
mySet.size  // 4

Some fun with Strings :

1
2
> (0/0 + "").repeat(10)+ " batman!"
'NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN batman!'

All these without pollyfills or shims is not bad at all!

Until next time, happy hacking!

Update 0: Don't use typeof flag!

Comments