Brooke's Notes

Experience, Record, Share.

Class Inheritance From ES5 to ES6

| Comments

Test Case

For test-driven development, the test case always comes first. The Phone inherits the Node.js EventEmitter: a phone is a customized event emitter, only augmented with its own properties and methods.

A phone has its own name, can powerOn itself. Since it’s an event emitter, it can emit ‘income’ event, and can set the handler by addListener as event emitter.

test.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var EventEmitter = require('events').EventEmitter;

//var Phone = require('./phone-es5');
//var Phone = require('./phone-util');
var Phone = require('./phone-es6');   // all implementation should have same interface

var p1 = new Phone('p1');             // class instantiation with parameter
p1.powerOn();                         // invoke instance methodl

console.log(p1 instanceof Phone);     // test ISA object of super class
console.log(p1 instanceof EventEmitter);
console.log(p1);                      // print instance at run-time

var incomeListener = function(number) {
    console.log('incomming call from: ' + number);
};

p1.addListener('income', incomeListener);
p1.income('+4478123456');             // invoke instance method
Phone.vendor('xiaomi');               // static/class method

Expect the following desired output:

1
2
3
4
5
6
7
8
9
10
11
[Phone instance method] p1 is powered on.
true
true
Phone {
  domain: null,
  _events: {},
  _eventsCount: 0,
  _maxListeners: 20,
  name: 'p1' }
incomming call from: +4478123456
[Phone static method] vendor: xiaomi

Prototype-Based Inheritance in ES5

In fact, javascript object is prototype-based, there isn’t class at all, everything in javascript is object. There’s only Object Extension (extends an exists object to a new object), rather than Class Inheritance (create new subclass that inherits the parent class). To implement Inheritance is to build/extend the appropriate prototype chain. The prototype-based inheritance is more flexible, and it’s easy to emulate traditional textbook or java-like class or inheritance.

phone-es5.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var EventEmitter = require('events').EventEmitter;
function Phone(name) {
  EventEmitter.call(this);    // call parent's constructor
  this.setMaxListeners(20);   // customize with parent's method
  this.name = name;           // self field
};
Phone.prototype = new EventEmitter();
// same as: Phone.prototype = Object.create(EventEmitter.prototype);
Phone.prototype.constructor = Phone;
Phone.prototype.powerOn = function() {
  console.log('[Phone instance method] ' + this.name + ' is powered on.');
};
Phone.prototype.income = function(number) {
  this.emit('income', number);
};
Phone.vendor = function(name) {
  console.log('[Phone static method] vendor: ' + name);
}
module.exports = Phone;

Using Node.js util.inherits

Node.js util module provides syntactical sugar for easily implemention of class-like inheritance.

phone-util.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var EventEmitter = require('events').EventEmitter;
var util = require('util');
function Phone(name) {
  EventEmitter.call(this);    // call parent's constructor
  this.setMaxListeners(20);   // customize with parent's method
  this.name = name;           // self field
};
util.inherits(Phone, EventEmitter);
Phone.prototype.powerOn = function() {
  console.log('[Phone instance method] ' + this.name + ' is powered on.');
};
Phone.prototype.income = function(number) {
  this.emit('income', number);
};
Phone.vendor = function(name) {
  console.log('[Phone static method] vendor: ' + name);
}
module.exports = Phone;

util.inherits levarages Object.create() of ES5, the inherits line equals to:

1
2
3
4
5
6
7
8
9
Phone.super_ = EventEmitter;
Phone.prototype = Object.create(EventEmitter.prototype, {
  constructor: {
    value: Phone,
    enumerable: false,
    writable: true,
    configurable: true
  }
});

Class Syntax of ES6

From node-v4.0, lots of ES6 features are implemented by default(shipping). Such as block scoping, collections, promises and Arrow Functions. And Class syntax is also fully supported. But, note that class is just another syntactical sugar:

JavaScript classes are introduced in ECMAScript 6 and are syntactical sugar over JavaScript's existing prototype-based inheritance.
The class syntax is not introducing a new object-oriented inheritance model to JavaScript.
JavaScript classes provide a much simpler and clearer syntax to create objects and deal with inheritance.

With class syntax, class/inheritance can be implemented in a more straightforward manner.

phone-es6.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
'use strict'
var EventEmitter = require('events').EventEmitter

class Phone extends EventEmitter {
  constructor(name) {
    super()
    this.setMaxListeners(20)
    this.name = name
  }
  powerOn() {
    console.log('[Phone instance method] ' + this.name + ' is powered on.')
  }
  income(number) {
    this.emit('income', number)
  }
  static vendor(name) {
    console.log(`[Phone static method] vendor: ${name}`)
  }
}

module.exports = Phone

Note: ‘use strict’ is required, and we use the template string syntax in static method (class method). And class declaration (as well as let, const, and function in strict mode) cannot be hoisted, unlike var and function.

In summary, with ES6 support in Node.js v4.0, class inheritance has never been more easier. So, let’s embrace ES6.

Comments