Why is it necessary to set the prototype construct

2018-12-31 05:50发布

In the section about inheritance in the MDN article Introduction to Object Oriented Javascript, I noticed they set the prototype.constructor:

// correct the constructor pointer because it points to Person
Student.prototype.constructor = Student;  

Does this serve any important purpose? Is it okay to omit it?

12条回答
浅入江南
2楼-- · 2018-12-31 05:59

Given simple constructor function:

function Person(){
    this.name = 'test';
}


console.log(Person.prototype.constructor) // function Person(){...}

Person.prototype = { //constructor in this case is Object
    sayName: function(){
        return this.name;
    }
}

var person = new Person();
console.log(person instanceof Person); //true
console.log(person.sayName()); //test
console.log(Person.prototype.constructor) // function Object(){...}

By default (from the specification https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/constructor), all prototypes automatically get a property called constructor that points back to the function on which it is a property. Depending on the constructor, other properties and methods might be added to the prototype which is not a very common practice but still it is allowed for extensions.

So simply answering: we need make sure that the value in prototype.constructor is correctly set as it is supposed by the specification to be.

Do we have to always set correctly this value? It helps with debugging and makes internal structure consistent against specification. We should definitely when our API is being used by the thirdparties, but not really when the code is finally executed in the runtime.

查看更多
只若初见
3楼-- · 2018-12-31 06:07

I'd disagree. It isn't necessary to set the prototype. Take that exact same code but remove the prototype.constructor line. Does anything change? No. Now, make the following changes:

Person = function () {
    this.favoriteColor = 'black';
}

Student = function () {
    Person.call(this);
    this.favoriteColor = 'blue';
}

and at the end of the test code...

alert(student1.favoriteColor);

The color will be blue.

A change to the prototype.constructor, in my experience, doesn't do much unless you're doing very specific, very complicated things that probably aren't good practice anyway :)

Edit: After poking around the web for a bit and doing some experimentation, it looks like people set the constructor so that it 'looks' like the thing that is being constructed with 'new'. I guess I would argue that the problem with this is that javascript is a prototype language - there is no such thing as inheritence. But most programmers come from a background of programming that pushes inheritence as 'the way'. So we come up with all sorts of things to try and make this prototypical language a 'classic' language.. such as extending 'classes'. Really, in the example they gave, a new student is a person - it isn't 'extending' from another student.. the student is all about the person, and whatever the person is the student is as well. Extend the student, and whatever you've extended is a student at heart, but is customized to fit your needs.

Crockford is a bit crazy and overzealous, but do some serious reading on some of the stuff that he's written.. it'll make you look at this stuff very differently.

查看更多
宁负流年不负卿
4楼-- · 2018-12-31 06:07

No need for sugared function 'classes' or using 'New' these days. Use object literals.

The Object prototype is already a 'class'. When you define an object literal, it is already an instance of the prototype Object. These can also act as another object's prototype, etc.

const Person = {
  name: '[Person.name]',
  greeting: function() {
    console.log( `My name is ${ this.name || '[Name not assigned]' }` );
  }
};
// Person.greeting = function() {...} // or define outside the obj if you must

// Object.create version
const john = Object.create( Person );
john.name = 'John';
console.log( john.name ); // John
john.greeting(); // My name is John 
// Define new greeting method
john.greeting = function() {
    console.log( `Hi, my name is ${ this.name }` )
};
john.greeting(); // Hi, my name is John

// Object.assign version
const jane = Object.assign( Person, { name: 'Jane' } );
console.log( jane.name ); // Jane
// Original greeting
jane.greeting(); // My name is Jane 

// Original Person obj is unaffected
console.log( Person.name ); // [Person.name]
console.log( Person.greeting() ); // My name is [Person.name]

This is worth a read:

Class-based object-oriented languages, such as Java and C++, are founded on the concept of two distinct entities: classes and instances.

...

A prototype-based language, such as JavaScript, does not make this distinction: it simply has objects. A prototype-based language has the notion of a prototypical object, an object used as a template from which to get the initial properties for a new object. Any object can specify its own properties, either when you create it or at run time. In addition, any object can be associated as the prototype for another object, allowing the second object to share the first object's properties

查看更多
何处买醉
5楼-- · 2018-12-31 06:07

It is not necessary. It is just one of the many things traditional, OOP champions do to try to turn JavaScript's prototypical inheritance into classical inheritance. The only thing that the following

Student.prototype.constructor = Student; 

does, is that you now have a reference of the current "constructor".

In Wayne's answer, that has been marked as correct, you could the exact same thing that the following code does

Person.prototype.copy = function() {  
    // return new Person(this.name); // just as bad
    return new this.constructor(this.name);
};  

with the code below (just replace this.constructor with Person)

Person.prototype.copy = function() {  
    // return new Person(this.name); // just as bad
    return new Person(this.name);
}; 

Thank God that with ES6 classical inheritance purists can use language's native operators like class, extends and super and we don't have to see like prototype.constructor corrections and parent refereces.

查看更多
零度萤火
6楼-- · 2018-12-31 06:10

So far confusion is still there.

Following the original example, as you have an existing object student1 as:

var student1 = new Student("Janet", "Applied Physics");

Suppose you don't want to know how student1 is created, you just want another object like it, you can use the constructor property of student1 like:

var student2 = new student1.constructor("Mark", "Object-Oriented JavaScript");

Here it will fail to get the properties from Student if the constructor property is not set. Rather it will create a Person object.

查看更多
零度萤火
7楼-- · 2018-12-31 06:15

It's not always necessary, but it does have its uses. Suppose we wanted to make a copy method on the base Person class. Like this:

// define the Person Class  
function Person(name) {
    this.name = name;
}  

Person.prototype.copy = function() {  
    // return new Person(this.name); // just as bad
    return new this.constructor(this.name);
};  

// define the Student class  
function Student(name) {  
    Person.call(this, name);
}  

// inherit Person  
Student.prototype = Object.create(Person.prototype);

Now what happens when we create a new Student and copy it?

var student1 = new Student("trinth");  
console.log(student1.copy() instanceof Student); // => false

The copy is not an instance of Student. This is because (without explicit checks), we'd have no way to return a Student copy from the "base" class. We can only return a Person. However, if we had reset the constructor:

// correct the constructor pointer because it points to Person  
Student.prototype.constructor = Student;

...then everything works as expected:

var student1 = new Student("trinth");  
console.log(student1.copy() instanceof Student); // => true
查看更多
登录 后发表回答