本文翻译自:Why is it necessary to set the prototype constructor?
In the section about inheritance in the MDN article Introduction to Object Oriented Javascript , I noticed they set the prototype.constructor: 在MDN文章“面向对象的Java语言简介”中有关继承的部分中 ,我注意到它们设置了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? 可以省略吗?
参考:https://stackoom.com/question/ztf1/为什么需要设置原型构造函数
Got a nice code example of why it is really necessary to set the prototype constructor.. 有一个很好的代码示例,说明了为什么真的有必要设置原型构造函数。
function CarFactory(name){
this.name=name;
}
CarFactory.prototype.CreateNewCar = function(){
return new this.constructor("New Car "+ this.name);
}
CarFactory.prototype.toString=function(){
return 'Car Factory ' + this.name;
}
AudiFactory.prototype = new CarFactory(); // Here's where the inheritance occurs
AudiFactory.prototype.constructor=AudiFactory; // Otherwise instances of Audi would have a constructor of Car
function AudiFactory(name){
this.name=name;
}
AudiFactory.prototype.toString=function(){
return 'Audi Factory ' + this.name;
}
var myAudiFactory = new AudiFactory('');
alert('Hay your new ' + myAudiFactory + ' is ready.. Start Producing new audi cars !!! ');
var newCar = myAudiFactory.CreateNewCar(); // calls a method inherited from CarFactory
alert(newCar);
/*
Without resetting prototype constructor back to instance, new cars will not come from New Audi factory, Instead it will come from car factory ( base class ).. Dont we want our new car from Audi factory ????
*/
This has the huge pitfall that if you wrote 如果您写的话,这有很大的陷阱
Student.prototype.constructor = Student;
but then if there was a Teacher whose prototype was also Person and you wrote 但是如果有一位老师的原型也是人,而你写了
Teacher.prototype.constructor = Teacher;
then the Student constructor is now Teacher! 那么学生构造函数现在是老师!
Edit: You can avoid this by ensuring that you had set the Student and Teacher prototypes using new instances of the Person class created using Object.create, as in the Mozilla example. 编辑:可以避免这种情况,方法是确保已使用使用Object.create创建的Person类的新实例设置了Student和Teacher原型,如Mozilla示例中所示。
Student.prototype = Object.create(Person.prototype);
Teacher.prototype = Object.create(Person.prototype);
So far confusion is still there. 到目前为止,混乱仍然存在。
Following the original example, as you have an existing object student1
as: 遵循原始示例,因为您已有一个对象student1
:
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: 假设您不想知道student1
的创建方式,只想要另一个对象,则可以使用student1
的构造方法属性,例如:
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. 如果未设置构造函数属性,将无法从Student
获取属性。 Rather it will create a Person
object. 而是将创建一个Person
对象。
Does this serve any important purpose? 这有任何重要目的吗?
Yes and no. 是的,没有。
In ES5 and earlier, JavaScript itself didn't use constructor
for anything. 在ES5和更早版本中,JavaScript本身不使用constructor
。 It defined that the default object on a function's prototype
property would have it and that it would refer back to the function, and that was it . 它定义了函数的prototype
属性上的默认对象将拥有它,并且它将引用回该函数,仅此而已 。 Nothing else in the specification referred to it at all. 规范中没有其他内容。
That changed in ES2015 (ES6), which started using it in relation to inheritance hierarchies. 这在ES2015(ES6)中发生了变化,ES2015开始在继承层次结构中使用它。 For instance, Promise#then
uses the constructor
property of the promise you call it on (via SpeciesConstructor ) when building the new promise to return. 例如, Promise#then
在constructor
新的诺言要返回时使用您通过(通过SpeciesConstructor )对其调用的诺言的constructor
属性。 It's also involved in subtyping arrays (via ArraySpeciesCreate ). 它也涉及子类型化数组(通过ArraySpeciesCreate )。
Outside of the language itself, sometimes people would use it when trying to build generic "clone" functions or just generally when they wanted to refer to what they believed would be the object's constructor function. 在语言本身之外,有时人们会在尝试构建通用的“克隆”函数时使用它,或者只是在想要引用他们认为是对象的构造函数的对象时才使用它。 My experience is that using it is rare, but sometimes people do use it. 我的经验是,很少使用它,但有时人们会使用它。
Is it okay to omit it? 可以省略吗?
It's there by default, you only need to put it back when you replace the object on a function's prototype
property: 默认情况下,它在那里,当您在函数的prototype
属性上替换对象时,只需要将其放回原处:
Student.prototype = Object.create(Person.prototype);
If you don't do this: 如果您不这样做:
Student.prototype.constructor = Student;
...then Student.prototype.constructor
inherits from Person.prototype
which (presumably) has constructor = Person
. ...然后Student.prototype.constructor
继承自Person.prototype
,该人(大概)具有constructor = Person
。 So it's misleading. 因此,这具有误导性。 And of course, if you're subclassing something that uses it (like Promise
or Array
) and not using class
¹ (which handles this for you), you'll want to make sure you set it correctly. 当然,如果要对使用它的东西进行子类化(例如Promise
或Array
),而不使用class
¹(可以为您处理),则需要确保正确设置。 So basically: It's a good idea. 基本上,这是个好主意。
It's okay if nothing in your code (or library code you use) uses it. 如果您的代码(或您使用的库代码)中没有任何东西使用它,那没关系。 I've always ensured it was correctly wired up. 我一直确保它已正确连接。
Of course, with ES2015 (aka ES6)'s class
keyword, most of the time we would have used it, we don't have to anymore, because it's handled for us when we do 当然,有了ES2015(aka ES6)的class
关键字,大多数时候我们会用它,我们不再需要了,因为当我们这样做时,它会为我们处理
class Student extends Person {
}
¹ "...if you're subclassing something that uses it (like Promise
or Array
) and not using class
..." — It's possible to do that, but it's a real pain (and a bit silly). ¹ “ ...如果您将使用它的东西(例如Promise
或Array
)子类化而不使用class
...” – 可以这样做,但这确实很痛苦(有点傻)。 You have to use Reflect.construct
. 您必须使用Reflect.construct
。
TLDR; TLDR; Not super necessary, but will probably help in the long run, and it is more accurate to do so. 这不是超级必要,但从长远来看可能会有所帮助,这样做更准确。
NOTE: Much edited as my previous answer was confusingly written and had some errors that I missed in my rush to answer. 注意:由于我先前的答案写得很混乱,因此进行了很多编辑,并且有一些我匆忙回答时错过的错误。 Thanks to those who pointed out some egregious errors. 感谢那些指出一些严重错误的人。
Basically, it's to wire subclassing up correctly in Javascript. 基本上,这是在Javascript中正确连接子类。 When we subclass, we have to do some funky things to make sure that the prototypal delegation works correctly, including overwriting a prototype
object. 当我们子类化时,我们必须做一些时髦的事情以确保原型委托能够正确工作,包括覆盖prototype
对象。 Overwriting a prototype
object includes the constructor
, so we then need to fix the reference. 覆盖prototype
对象包括constructor
,因此我们需要修复引用。
Let's quickly go through how 'classes' in ES5 work. 让我们快速了解一下ES5中“类”的工作方式。
Let's say you have a constructor function and its prototype: 假设您有一个构造函数及其原型:
//Constructor Function
var Person = function(name, age) {
this.name = name;
this.age = age;
}
//Prototype Object - shared between all instances of Person
Person.prototype = {
species: 'human',
}
When you call the constructor to instantiate, say Adam
: 当调用构造函数实例化时,请说Adam
:
// instantiate using the 'new' keyword
var adam = new Person('Adam', 19);
The new
keyword invoked with 'Person' basically will run the Person constructor with a few additional lines of code: 使用'Person'调用的new
关键字基本上将使用以下几行代码来运行Person构造函数:
function Person (name, age) {
// This additional line is automatically added by the keyword 'new'
// it sets up the relationship between the instance and the prototype object
// So that the instance will delegate to the Prototype object
this = Object.create(Person.prototype);
this.name = name;
this.age = age;
return this;
}
/* So 'adam' will be an object that looks like this:
* {
* name: 'Adam',
* age: 19
* }
*/
If we console.log(adam.species)
, the lookup will fail at the adam
instance, and look up the prototypal chain to its .prototype
, which is Person.prototype
- and Person.prototype
has a .species
property, so the lookup will succeed at Person.prototype
. 如果我们console.log(adam.species)
,则在adam
实例上查找将失败,并在原型链上查找其.prototype
,即Person.prototype
,而Person.prototype
具有 .species
属性,因此查找将在Person.prototype
成功。 It will then log 'human'
. 然后它将记录为'human'
。
Here, the Person.prototype.constructor
will correctly point to Person
. 在这里, Person.prototype.constructor
将正确指向Person
。
So now the interesting part, the so-called 'subclassing'. 现在是有趣的部分,即所谓的“子类化”。 If we want to create a Student
class, that is a subclass of the Person
class with some additional changes, we'll need to make sure that the Student.prototype.constructor
points to Student for accuracy. 如果我们要创建一个Student
类,它是Person
类的子类,并进行了一些其他更改,则需要确保Student.prototype.constructor
指向Student以确保准确性。
It doesn't do this by itself. 它本身并不执行此操作。 When you subclass, the code looks like this: 当您子类化时,代码如下所示:
var Student = function(name, age, school) {
// Calls the 'super' class, as every student is an instance of a Person
Person.call(this, name, age);
// This is what makes the Student instances different
this.school = school
}
var eve = new Student('Eve', 20, 'UCSF');
console.log(Student.prototype); // this will be an empty object: {}
Calling new Student()
here would return an object with all of the properties we want. 在这里调用new Student()
将返回一个具有我们想要的所有属性的对象。 Here, if we check eve instanceof Person
, it would return false
. 在这里,如果我们检查eve instanceof Person
,它将返回false
。 If we try to access eve.species
, it would return undefined
. 如果我们尝试访问eve.species
,它将返回undefined
。
In other words, we need to wire up the delegation so that eve instanceof Person
returns true and so that instances of Student
delegate correctly to Student.prototype
, and then Person.prototype
. 换句话说,我们需要连接该委托,以便eve instanceof Person
返回true,以便Student
实例正确地委托给Student.prototype
,然后依次Person.prototype
。
BUT since we're calling it with the new
keyword, remember what that invocation adds? 但是由于我们使用new
关键字进行调用,还记得该调用添加了什么吗? It would call Object.create(Student.prototype)
, which is how we set up that delegational relationship between Student
and Student.prototype
. 它将调用Object.create(Student.prototype)
,这是我们在Student
和Student.prototype
之间建立委托关系的方式。 Note that right now, Student.prototype
is empty. 请注意,现在, Student.prototype
为空。 So looking up .species
an instance of Student
would fail as it delegates to only Student.prototype
, and the .species
property doesn't exist on Student.prototype
. 因此,查找.species
的Student
实例将失败,因为它仅委托给Student.prototype
,而.species
属性在Student.prototype
上不存在。
When we do assign Student.prototype
to Object.create(Person.prototype)
, Student.prototype
itself then delegates to Person.prototype
, and looking up eve.species
will return human
as we expect. 当我们确实将Student.prototype
分配给Object.create(Person.prototype)
, Student.prototype
本身将委托给Person.prototype
,并且查找eve.species
将返回我们期望的human
。 Presumably we would want it to inherit from Student.prototype AND Person.prototype. 大概我们希望它继承自Student.prototype和Person.prototype。 So we need to fix all of that. 因此,我们需要修复所有问题。
/* This sets up the prototypal delegation correctly
*so that if a lookup fails on Student.prototype, it would delegate to Person's .prototype
*This also allows us to add more things to Student.prototype
*that Person.prototype may not have
*So now a failed lookup on an instance of Student
*will first look at Student.prototype,
*and failing that, go to Person.prototype (and failing /that/, where do we think it'll go?)
*/
Student.prototype = Object.create(Person.prototype);
Now the delegation works, but we're overwriting Student.prototype
with an of Person.prototype
. 现在委派工作了,但是我们用Person.prototype
覆盖了Student.prototype
。 So if we call Student.prototype.constructor
, it would point to Person
instead of Student
. 因此,如果我们调用Student.prototype.constructor
,它将指向Person
而不是Student
。 This is why we need to fix it. 这就是为什么我们需要修复它。
// Now we fix what the .constructor property is pointing to
Student.prototype.constructor = Student
// If we check instanceof here
console.log(eve instanceof Person) // true
In ES5, our constructor
property is a reference that refers to a function that we've written with the intent to be a 'constructor'. 在ES5中, constructor
属性是一个引用,引用了我们为成为“构造函数”而编写的函数。 Aside from what the new
keyword gives us, the constructor is otherwise a 'plain' function. 除了new
关键字给我们提供的功能外,构造函数还可以是“普通”函数。
In ES6, the constructor
is now built into the way we write classes - as in, it's provided as a method when we declare a class. 在ES6中, constructor
现已内置到我们编写类的方式中-就像在声明类时将其作为方法提供一样。 This is simply syntactic sugar but it does accord us some conveniences like access to a super
when we are extending an existing class. 这只是语法糖,但确实为我们提供了一些便利,例如在扩展现有类时访问super
。 So we would write the above code like this: 所以我们将这样编写上面的代码:
class Person {
// constructor function here
constructor(name, age) {
this.name = name;
this.age = age;
}
// static getter instead of a static property
static get species() {
return 'human';
}
}
class Student extends Person {
constructor(name, age, school) {
// calling the superclass constructor
super(name, age);
this.school = school;
}
}