There are several questions here:
Can you please explain the difference between the following blocks of code. I tested and both blocks work.
The first only creates one Drive function, the second creates two of them: one on myCar and another one on mySuperCar.
Here is code that would give different results when either the first or second block was executed:
myCar.Fly === mySuperCar.Fly // true only in the first case
Object.keys(myCar).includes("Fly") // true only in the second case
Object.keys(Car.prototype).length === 0 // true only in the second case
What's the best practice and why?
Why did the author add Drive and Fly methods using prototype, but doesn't declare them as this.Drive method inside Car class and this.Fly in SuperCar class?
It is better practice to define methods on the prototype, because:
- each method is only defined once
- each method is also available to instances that were created without executing the constructor (which is the case when calling
Object.create(Car.prototype));
- you can inspect at which level in an instance's prototype chain a certain method got defined.
Why does SuperCar.prototype.constructor need to be set back to SuperCar? Is constructor property overridden when prototype is set? I commented out this line and nothing changed.
The constructor property is not overridden when prototype is set. But the constructor of new Car() is Car, so if you set new Car() to SuperCar.prototype, then obviously SuperCar.prototype.constructor is Car.
As long as you don't reassign to prototype, there is an invariance: Constructor.prototype.constructor === Constructor. For example, this is true for Car: Car.prototype.constructor === Car, but it is equally true for Array, Object, String, ...etc.
But if you reassign a different object to prototype, that invariance is broken. Usually this is not a problem (as you have noticed), but it is better to reinstate it, because it answers the question "Which constructor uses this prototype object when creating new instances?" Some code may do such inspection and depend on it. See "Why is it necessary to set the prototype constructor?" for such cases.
Why call Car.call(this, name); in SuperCar constructor? Won't properties and methods of Car be 'inherited' when I do
var myCar = new Car("Car");
If you don't do Car.call(this, name); then your SuperCar instance will not have a name property. You could of course decide to just do this.name = name; instead, which just copies the code that is in the Car constructor, but in more complex situations it would be bad practice to have such code duplication.
It would not be helpful to call new Car(name) within the SuperCar constructor, as that will create another object, while you really need to extend the this object. By not using new (using call instead) you actually tell the Car function to not run as a constructor (i.e. to not create a new object), but to use the object you pass to it instead.
Times have changed
In modern versions of JavaScript you can use super(name) instead of Car.call(this, name):
function SuperCar(name) {
super(name);
}
Today, you would also use the class syntax and write the first code block from the question as follows:
class Car {
constructor(name) {
this.name = name;
}
drive() {
console.log(`My name is ${this.name} and I'm driving.`);
}
}
class SuperCar extends Car {
constructor(name) {
super(name);
}
fly() {
console.log(`My name is ${this.name} and I'm flying!`);
}
}
const myCar = new Car("Car");
myCar.drive();
const mySuperCar = new SuperCar("SuperCar");
mySuperCar.drive();
mySuperCar.fly();
Note how you don't even have to mention the prototype property to achieve the goal. The class ... extends syntax also takes care of setting the prototype.constructor property as the first block in your question did.