Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Javascript - the difference between parent.call(arguments) and child.prototype = new parent(arguments)?

I'm trying to get my head fully wrapped around prototypal inheritance and Javascript's inheritance system.

I'm trying to figure out what the difference would be between these two examples. I'm going to guess that it has to do with the prototype inheritance chain, but I'm having trouble finding information relating the two.

Example 1 - call()

function Vehicle(wheels) {
    this.wheels = wheels;
}

function Car(doors) {
    this.doors = doors;
    Vehicle.call(this,4);
}

Example 2 - prototype

function Vehicle(wheels) {
    this.wheels = wheels;
}

function Car(doors) {
    this.doors = doors;
}
Car.prototype = new Vehicle(4);
like image 352
Kris Avatar asked Jul 19 '26 22:07

Kris


2 Answers

In the first example you're initializing your Car object to have all the same properties of a new Vehicle object. However, it's not "real" inheritance, in the following ways:

var car = new Car(2);
car instanceof Vehicle; // => false

That's the first thing: instanceof won't work. Here's the second:

var car = new Car(2);

Vehicle.prototype.drive = function() {
  console.log('vroom!');
};

car.drive(); // throws

So that's the second thing: accesses to a Car instance won't reach up the prototype chain to Vehicle's prototype.

With that in mind, I'd tend to favor the second approach.


That said, you might actually want to do both, as there could be per-instance initialization logic in your Vehicle constructor that you actually want to execute when you initialize a Car (and that won't happen just by setting the prototype).

To understand what I mean: in your examples you're always passing 4 as the wheels parameter to the Vehicle constructor, which makes sense. But suppose we add another parameter that can vary by instance:

function Vehicle(wheels, color) {
  this.wheels = wheels;
  this.color = color;
}

Now there are two approaches we could take here:

function Car(doors, color) {
  this.doors = doors;
  Vehicle.call(this, 4, color);
}

Car.prototype = new Vehicle();

Notice here that rather than pass anything to the Vehicle constructor when setting Car's prototype, we actually call the constructor explicitly in Car. This way we get color set properly.

The other approach would be:

function Car(doors, color) {
  this.doors = doors;
  this.color = color;
}

Car.prototype = new Vehicle(4, null);

That would work too.

like image 91
Dan Tao Avatar answered Jul 22 '26 11:07

Dan Tao


The interpreter adds an internal reference (you can call proto if you will) that points to the prototype of the corresponding constructor.

If you run the first example and instantiate two instances of each constructor you will end up having something like the following:

enter image description here

Things that you need to notice here are:

  • The carsObj has one additional property that the Car constructor doesn't define. The reason for the is this line Vehicle.call(this,4); that you have inside the Car constructor.
  • Both the constructors and their instances point to an object that is called prototype; This is where the shared members can be stored (i.e. methods or shared properties). In addition, this allows to create inheritance hierarchies via the prototype chaining.
  • Instances of the car object will never be able to access the members of the Vehicle's prototype.

The last bullet point is what restricts up from applying proper inheritance.

Similarly if you run the second example the memory might look like this:

enter image description here

The difference is quite evident. The prototype property of the Car constructor points to an instance of the Vehicle constructor, and so do all the instances of the Car constructor. This is due to this line Car.prototype = new Vehicle(4);.

This structure allows the instances of the Car constructor to access all the properties that are defined on the prototype of the parent class. This is due to how JavaScript traverses th prototype chained when looking for a property.

The shortcoming of this technique is that the Car instances do not inherit any instance properties from the Vehicle constructor. It inherits only the shared members.

A better solution is the combination of the above two techniques; That is:

function Vehicle(wheels) {
   this.wheels = wheels;
}

function Car(doors) {
   this.doors = doors;
   Vehicle.call(this, 4)
}

Car.prototype = Object.create(Vehicle);

This way instances of the Car constructor will inherit both the shared and the instance members.

like image 37
ppoliani Avatar answered Jul 22 '26 12:07

ppoliani