简体   繁体   中英

Javascript prototype constructor explanation

I have the following code snippet which represents the answer to this question:

You are creating a class named Consultant that mustinherit from the Employee class. The Consultant class must modify the inherited PayEmployee method. Future instances of Consultant must be created withthe overridden method.

function Employee() {}

Employee.prototype.PayEmployee = function ( ){
  alert(‘Hi there!’);
}

function Consultant () { 
  Employee.call(this);                            // X
}
Consultant.prototype = new Employee(); 
Consultant.prototype.constructor = Consultant;    // X

Consultant.prototype.PayEmployee = function () 
{ 
  alert(‘Pay Consultant’); 
}

I really can't wrap my head around this. Why are lines marked with "X" necessary? Couldn't I just write the code without these lines and it would be exactly the same? According to my tests it would make no difference:

Can someone explain me what the difference between these two codes is, and what why I should use one over the other?

Thanks in advance

Couldn't I just write the code without these lines and it would be exactly the same?

No, you do need both of them in the general case. Yes, in some specific cases, you can get away without one or both of them.

First off, note that that code has a common error in it. This is incorrect:

Consultant.prototype = new Employee(); // INCORRECT

It should be:

Consultant.prototype = Object.create(Employee.prototype);

new Employee does two distinct things:

  1. It creates an object that uses Employee.prototype as its prototype

  2. It runs the code in Employee , whose job is to initialize an instance of Employee by setting up properties and such on this

But we don't want that second part yet. Consultant.prototype isn't an Employee instance , it's an object that will become the prototype of an object created via new Consultant (which will be an instance of Employee , and also of Consultant ). (This is where JavaScript's constructor functions and prototype property depart from standard prototypical inheritance. You can do standard prototypical inheritance in JavaScript, but when you do, you don't use constructor functions, the prototype property, or new .)

Object.create just does the first of those two tasks: Creates an object using Employee.prototype as its prototype. So we end up with an object on Consultant.prototype that uses Employee.prototype as its prototype, but without calling Employee and having it run its per-instance initialization code. (We do that later, in the second line you marked with an X.)

But why don't we want Employee to do its per-instance initialization? Because, again, Consultant.prototype isn't an Employee instance, so it doesn't make sense to initialize it as one. For one thing: What if Employee requires parameters it uses to initialize the object it returns? What would we pass it when constructing Consultant.prototype ? We don't have any instance-specific information to pass it at that point.

So instead, we just do #1 above (via Object.create ), and leave #2 (calling Employee ) until we're constructing an instance, by calling Employee.call(this) from Consultant (more below).


To your marked lines:

Employee.call(this);

This line is essential to give Employee its chance to do its initialization of the instance being created. Without it, Employee hasn't had a chance to do its job: Initialize the Employee parts of the instance. For instance, suppose Employee looks like this:

function Employee() {
    this.paySchedule = "biweekly";
}

Without the Employee.call(this); line in Consultant , that property would be missing from an instance created via new Consultant .

Your second marked line:

Consultant.prototype.constructor = Consultant;

We need to do this because Consultant.prototype 's constructor property should point to Consultant , but if we don't do this, it will point to Employee .

It didn't used to matter much, because although constructor was defined in the specification to be set up this way by default, nothing in the specificaton used to use it. That changed in ES2015 (aka "ES6"): Now, the constructor property is used sometimes (for instance, in promises).


Here's an example illustrating the situation where Employee has parameters. I've also switched PayEmployee to payEmployee to be in keeping with the overwhelming convention in JavaScript that only constructor functions are initially-capped:

 function Employee(name, title) { this.name = name; this.title = title; } Employee.prototype.payEmployee = function() { console.log("Time to pay " + this.name + " (" + this.title + ")"); }; function Consultant(name) { Employee.call(this, name, "Consultant"); } Consultant.prototype = Object.create(Employee.prototype); Consultant.prototype.constructor = Consultant; Consultant.prototype.payEmployee = function() { console.log("Time to pay " + this.name + " (" + this.title + ") -- remember, you're paying the gross, consultants handle their own tax."); }; var e = new Employee("Joe Bloggs", "Engineer"); e.payEmployee(); var c = new Consultant("John Smith"); c.payEmployee(); 


And finally, I'd be remiss if I didn't point out that as of ES2015, we have class syntax for this, which can be transpiled if necessary for older environments:

 class Employee { constructor(name, title) { this.name = name; this.title = title; } payEmployee() { console.log("Time to pay " + this.name + " (" + this.title + ")"); } } class Consultant extends Employee { constructor(name) { super(name, "Consultant"); } payEmployee() { console.log("Time to pay " + this.name + " (" + this.title + ") -- remember, you're paying the gross, consultants handle their own tax."); } } const e = new Employee("Joe Bloggs", "Engineer"); e.payEmployee(); const c = new Consultant("John Smith"); c.payEmployee(); 


You asked about whether Consultant.prottype = new Employee(); is always wrong. Let's take a version of the code using new Employee for that, and removing the two lines you marked with X, and add a feature to Employee that seems like it should work, but ends up leading to all kinds of confusion:

 function Employee() { this.staff = []; } Employee.prototype.payEmployee = function() { console.log("Paying employee"); }; function Consultant(name) { } Consultant.prototype = new Employee(); Consultant.prototype.payEmployee = function() { console.log("Time to pay " + this.name + " (" + this.title + ") -- remember, you're paying the gross, consultants handle their own tax."); }; var jill1 = new Employee(); jill1.staff.push("Bob"); jill1.staff.push("Jane"); var john1 = new Employee(); john1.staff.push("Russell"); john1.staff.push("Mohammed"); console.log("Jill's staff (1): " + jill1.staff.join(", ")); console.log("John's staff (1): " + john1.staff.join(", ")); // so far so good, but what if we use Consultant instead? var jill2 = new Consultant(); jill2.staff.push("Bob"); jill2.staff.push("Jane"); var john2 = new Consultant(); john2.staff.push("Russell"); john2.staff.push("Mohammed"); console.log("Jill's staff (2): " + jill2.staff.join(", ")); console.log("John's staff (2): " + john2.staff.join(", ")); // ??? how is it they both have all four staff?! 

The problem is that the staff array is on Consultant.prototype , not each instance, and so regardless of which instance we use, we're always accessing the same array.

That's why we don't use functions intended to initialize instances (eg, constructor functions) to initialize the object for a constructor function's prototype property.

I should note that in standard prototypical inheritance, it would be common and normal to actually create an instance to be a prototype of another instance. But JavaScript's constructor functions and their prototype property are not standard prototypical inheritance, they're a hybrid of class-based OOP and prototypical OOP. You can do pure prototypical OOP in JavaScript (and many do), but when you do, you don't use constructor functions, their prototype property, or new . You use factory functions and Object.create (usually).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM