5

I have been using Node since 0.11/0.12 so correct me if this is a matter of coming relatively late to the party.

I am trying to understand the difference between using util.inherits(Son, Dad) and simply extending the prototype of Son.prototype = [new] Dad().

For this example I am subclassing a Transform stream using util.inherits first:

var util = require('util')
var Transform = require('stream').Transform

util.inherits(TStream, Transform)

function TStream () {
  Transform.call(this)
}

TStream.prototype._transform = function(chunk, encoding, done) {
  this.push(/* transform chunk! */)
  done()
}

process.stdin.pipe(new TStream()).pipe(process.stdout)

The above seems to be the most common way to go about this in Node. The following (extending the prototype) works just as well (seemingly), and it's simpler:

function TStream() {}
TStream.prototype = require("stream").Transform()

TStream.prototype._transform = function (chunk, encoding, done) {
  this.push(/* transform chunk! */)
  done()
}

process.stdin.pipe(new TStream()).pipe(process.stdout)

For the record, I know there is through2, which has a very simple interface, and do help reducing a few lines of code (see below), but I am trying to understand what is going under the hood, hence the question.

var thru = require("through2")(function (chunk, encoding, done) {
  this.push(/* transform chunk! */)
  done()
})

process.stdin.pipe(stream).pipe(process.stdout)

So, what are the differences between util.inherits and extending the prototype in Node?

Jorge Bucaran
  • 5,588
  • 2
  • 30
  • 48
  • First of; you should not set the prototype of Child to an instance of Parent as it shows a lack of understanding what prototype is. Prototype is shared and Parent may have instance specific members that end up on Child.prototype. If these members are mutable and you don't re use Parent constructor (doing `Parent.call(this)` in Child constructor) you will have Child instances mutating a shared member that should be instance specific. Use `Child.prototype = Object.create(Parend.prototype)` instead. – HMR Apr 08 '15 at 07:56

1 Answers1

8

If this is the implementation of util.inherits it only does the following for you:

  Child.super_ = Parent;
  //set prototype using Object.create
  Child.prototype = Object.create(Parent.prototype, {
    constructor: {//repair the prototype.constructor
      value: Child,
      enumerable: false,
      writable: true,
      configurable: true
    }

This is not a problem in Nodejs but in browsers that don't support a second argument to Object.create (because the polyfil does not allow it) you can repair the constructor in the following way:

Child.prototype = Object.create(Parent.prototype);//if you polyfilled Object.create
//Child.prototype.constructor is now Parent so we should repair it
Child.prototype.constructor = Child;

The extra thing it does is setting Child.super_ so in Child you can do:

function Child(){
  Child.super_.call(this);//re use parent constructor
  //same as Parent.call(this);
}

For more information on prototype and constructor functions you can read this answer.

According to the following, you are sub classing Transform incorrectly:

In classes that extend the Transform class, make sure to call the constructor so that the buffering settings can be properly initialized.

So the correct code should call it's constructor (you are not calling Transform with new but maybe the constructor has a way of handling faulty calls).

var Transform = require("stream").Transform;
function TStream() {
  Transform.call(this);//you did not do that in your second example
}
//your code sets prototype to an INSTANCE of Transform
//  and forgets to call the constructor with new
//TStream.prototype = require("stream").Transform()
TStream.prototype = Object.create(Transform.prototype);
TStream.prototype.constructor = TStream;
TStream.prototype._transform = function (chunk, encoding, done) {
  this.push(/* transform chunk! */)
  done()
}
process.stdin.pipe(new TStream()).pipe(process.stdout)
Community
  • 1
  • 1
HMR
  • 37,593
  • 24
  • 91
  • 160
  • Can I replace `Object.create(Transform.prototype)` with `new Transform()`. Thanks, if the correct implementation looks more like what you wrote there, then using `util.inherits` is clearly better, or rather `through2`. – Jorge Bucaran Apr 08 '15 at 10:03
  • @JorgeBucaran Creating an instance of Parent to be used as Child.prototype is a bad idea. The link to the other answer explains why. It may accidentally work by doing it but its better to use Object.create – HMR Apr 08 '15 at 14:26
  • Thanks! @HMR I will accept your answer. Can you explain why is `TStream.prototype.constructor = TStream` needed? I think changing the prototype does not affect TStream's constructor and the example works just as well without it. – Jorge Bucaran Apr 09 '15 at 01:38
  • @JorgeBucaran Constructor is something that comes with prototype and should point to the constructor function. When you set the prototype of Child it points to Parent and code may break. It is rarely used but still better to have it point to the actual constructor. More info is in the other answer that's linked in this answer – HMR Apr 09 '15 at 02:03