2

I've been trying to understand why I see code like this in node all the time:

var util = require("util");
var events = require("events");

function MyStream() {
    events.EventEmitter.call(this);
}

util.inherits(MyStream, events.EventEmitter);

MyStream.prototype.write = function(data) {
    this.emit("data", data);
}

I went to the documentation in order to better understand.

util.inherits(constructor, superConstructor)

The prototype of constructor will be set to a new object created from superConstructor.

As an additional convenience, superConstructor will be accessible through the constructor.super_ property.

From that I assumed that meant that all util.inherits does is this:

exports.inherits = function (constructor, superConstructor) {
    constructor.super_ = superConstructor;
    constructor.prototype = new superConstructor();
}

Seemed to make sense to me, but then I noticed in the MyStream constructor they invoke event.EventEmitter.call(this); so that the EventEmitter constructor runs on the instance of MyStream being created. I was totally confused by this since constructor.prototype = new superConstructor(); should be all it needed right? Well I went to the source and found the actual function signature.

exports.inherits = function(ctor, superCtor) {
  ctor.super_ = superCtor;
  ctor.prototype = Object.create(superCtor.prototype, {
    constructor: {
      value: ctor,
      enumerable: false,
      writable: true,
      configurable: true
    }
  });
};

Once I saw this I got totally confused while trying to figure out what Object.create is doing here. Why is it doing this instead of just new superCtor()? I tried looking at the documentation for Object.create but I'm still confused. Let me try and see if I can articulate what I think is going on and you guys can correct me :)

The first argument to Object.create(proto [, propertiesObject ]) is the prototype that you want this new object to have. I guess this is just easier than the classic method of defining a constructor and setting its prototype property? The second argument is a simple hash with properties on it that should be added to the newly created object that it will return? So, this...

var myInstance = Object.create(somePrototype, { someProp: someVal, someProp2: someVal2);

...instead of this?

function MyClass () {
    this.prop = someVal;
    this.prop2 = someVal2;
    // etc
}
MyClass.prototype = somePrototype;
var myInstance = new MyClass();

I'm not sure if my understanding of Object.create is totally accurate so hopefully you guys can clarify. Assuming that I now understand Object.create why then does the util.inherits function not look like this?

exports.inherits = function(ctor, superCtor) {
  ctor.super_ = superCtor;
  ctor.prototype = Object.create(new superCtor, {
    constructor: {
      value: ctor,
      enumerable: false,
      writable: true,
      configurable: true
    }
  });
};

To Summarize

Wouldn't the new superCtor eliminate the need to do events.EventEmitter.call(this) inside of MyStream? Why is it so important that we make the prototypes the same and then run the EventEmitter constructor on the actual instance that MyStream is creating instead of just having the prototype be an actual instance of EventEmitter?

Also, what is the purpose of adding a constructor property to ctor's prototype? Is that a JavaScript thing I don't know about?

CatDadCode
  • 58,507
  • 61
  • 212
  • 318
  • What if `new superCtor` require arguments ? are you going to pass superCtor arguments to util.inherits too ? you cant. – mpm Jan 26 '14 at 22:32
  • This is how: `events.EventEmitter.bind(events, arg1, arg2, arg3, etc)`. But what does that have to do with the question I asked? You've only confused me further. How in the world does `Object.create` solve the problem you mention? `util.inherits` accepts no arguments no matter if it works the way it currently does or the way I thought it worked... – CatDadCode Jan 26 '14 at 23:11
  • that's precisely why they dont use `new` in inherits. How do you know what arguments to bind? and why bind to `events` ? And that's why prototypal inheritance is at least a 2 step process. Using new doesnt solve the `Function.call(this)` problem. – mpm Jan 26 '14 at 23:38
  • Yeah I'm not really having the clouds parted. Maybe some examples of what you're talking about? It sounds to me like `util.inherits` is useless because I could just set the prototype myself like in my example and add the `super_` property if I really want to. Then I can use whatever arguments I want, then set the new instance as my prototype. – CatDadCode Jan 26 '14 at 23:40
  • Dont worry when JS gets `classes` you can forget all about this stuff,in one year or 2 maybe. You could do util.inherits without that function,it's just that they needed a "standard" way to set the `super_` variable on `this.constructor` , a bit like coffeescript has its own way to do prototypal inheritance. – mpm Jan 26 '14 at 23:42
  • exact duplicate of [What is the reason (not) to use the 'new' keyword here?](http://stackoverflow.com/questions/12592913/what-is-the-reason-to-use-the-new-keyword-here) – Bergi Jan 27 '14 at 00:36
  • This is most definitely NOT an exact duplicate of that question. – CatDadCode Jan 27 '14 at 01:29

1 Answers1

1

First parent may require arguments. When setting up Child.prototype you are defining the object and may not be ready to create instances yet.

Second parent instance specific values (this.someval) are put on the child's prototype and are then either shared among all child instances or become useless when Parent.apply(this,arguments) executes in the child's body and shadows them.

More info on constructor functions and prototype here : https://stackoverflow.com/a/16063711/1641941

Community
  • 1
  • 1
HMR
  • 37,593
  • 24
  • 91
  • 160
  • I'm still a bit confused but I think I'm starting to understand. I just wish I could find some straight forward code samples explaining why people do things like this as opposed to classical prototypal inheritance. – CatDadCode Jan 27 '14 at 15:24
  • 1
    @AlexFord The link shows the use of a helper function so you don't have to create an instance of Parent to set up the prototype part of Child's inheritance (as in Child being a Parent, not Child having a Parent). – HMR Jan 28 '14 at 01:25
  • 1
    @AlexFord Prototype is where shared members are put and setting the Prototype of Child to an instance of Parent would mean the Child has members in it's prototype from the Parent instance that should not be shared but instance specific. Look at the link in my answer, the first Hamster code and remove `Hamster.apply(this,arguments);` from RussionMini's body. It would cause the `food` member to be shared for all RussionMini's instances. Push food in from instance one (`instanceOne.food.push("from one")`) and it'll show up in instanceTwo.food – HMR Jan 28 '14 at 01:26
  • "Child being a Parent, not Child having a Parent" totally parted the clouds. Thank you very much. – CatDadCode Jan 28 '14 at 03:21
  • I didn't read through your entire answer on the linked question the last time I clicked it. My bad. That is one of the greatest answers of all time. Thank you very much. – CatDadCode Jan 28 '14 at 03:39