Rob Fox
Rob Fox

Reputation: 5571

'This' is not the same in constructor and inherited constructor

I have this simple inheritance pattern:

function Emitter() {
    this.dis1 = this;
};

function Person() {
    this.dis2 = this;
};
Person.prototype = new Emitter();
var p1 = new Person();
var p2 = new Person();

console.log(p1.dis1 === p2.dis1); //true - why?
console.log(p1.dis2 === p2.dis2); //false

Why are dis1 of separate objects the same? Is there a workaround for this?

Upvotes: 4

Views: 74

Answers (3)

Tibos
Tibos

Reputation: 27853

A note on how the new operator works:

Foo = function(){};
Foo.prototype;

var bar = new Foo();

What happens on the last line is a new object is created which inherits from Foo.prototype and then the code in the Foo function is executed with this being that new object. Then bar is initialized to that new object.


Your inheritance model has several flaws and you have uncovered one of them. All the flaws stem from this line of code:

Person.prototype = new Emitter();

This line says that the object all Person instances inherit from is an instance of Emitter (the instance of Emitter created there).

p1.dist1 looks up the dist1 property on the prototype chain. First it looks in p1 to see if it has the dist1 property and it does not. Next it looks in p1.[[Prototype]] which was initialized to Person.prototype where it does find the dist1 property and it's value is Person.prototype (the one instance of Emitter mentioned above).

p1.dist does the same thing, but the property is found in p1 and it is equal to p1.

The same thing happens with p2, p2.dist1 being the same instance of Emitter while p2.dist2 being equal to p2.


A way to do inheritance and avoid all these problems would be:

function Emitter() {
    this.dis1 = this;
};

function Person() {
    Emitter.call(this); // calls the parent constructor.
    this.dis2 = this;
};
Person.prototype = Object.create(Emitter.prototype); // you don't even use this line

var p1 = new Person();
var p2 = new Person();

In this model, p1.dist1 === p1 and p1.dist2 === p1, p2.dist1 === p2 and p2.dist2 === p2. What happens here is that the code in the Emitter constructor is called with this being the new Person object being created. So when you say in the Emitter constructor this.dis1 = this, this is actually p1 or p2.

The line with the prototype is not used in this example, but it does other cool things you might want:

  • Methods and properties declared on Emitter.prottoype will be available for p1 and p2
  • p1 instanceof Emitter will be true.

You can find this model also used on MDN.

Upvotes: 3

Denys Séguret
Denys Séguret

Reputation: 382514

The dis1 property is looked up in the prototype chain.

As it's not present in p1 or p2, it's looked up in the prototype, which is the object you created with new Emitter(). That's the reason why you have the same value.

The prototypal model in JavaScript is very different from the inheritance model you have in other languages like Java. Be careful not to try to emulate Java or C++ designs in JavaScript : that always lead to bad ones.

Now, suppose you want to have an object at the Emitter level and different for each Person instance, then the simplest solution might be to initialize the values on demand or using a specific function (you might use a convention to name it initialize and then override it).

Example with on demand initialization :

function Emitter() {
}
Emitter.prototype.register = function(thing){
  if (!this.listeners) {
      this.listeners = []; 
  }
  this.listeners.push(thing);
}

function Person() {
}
Person.prototype = new Emitter();

In fact the best solution here would probably, as often in real OOP languages, to prefer composition over inheritance and to simply set the emitter as a property of the person.

Upvotes: 2

basilikum
basilikum

Reputation: 10536

As dystroy already mentioned, dis1 gets looked up in the prototype chain. If you want to have different dis1s for different Persons, you can use the following pattern.

function Emitter() {
    this.dis1 = this;
};

function Person() {
    Emitter.call(this);
    this.dis2 = this;
};
Person.prototype = Object.create(Emitter.prototype);
var p1 = new Person();
var p2 = new Person();

console.log(p1.dis1 === p2.dis1); //false
console.log(p1.dis2 === p2.dis2); //false

Note that you have to call Emitter inside the Person constructor, while providing this as current context. Creating a new Person will now create an object that contains all the (local) properties of Person as well as all the (local) properties of Emitter in a flat hierarchy:

{
    dis1: Person,
    dis2: Person
}

You also can use Object.create to realize prototype inheritance. This has the advantage, that you don't actually call the Emitter constructor and thereby you don't get "local" properties like dis1 to be inside the prototype object.

Upvotes: 1

Related Questions