在MDN 文章面向对象的 Javascript 简介中关于继承的部分中,我注意到他们设置了prototype.constructor:
// correct the constructor pointer because it points to Person
Student.prototype.constructor = Student;
这有什么重要目的吗?省略它可以吗?
在MDN 文章面向对象的 Javascript 简介中关于继承的部分中,我注意到他们设置了prototype.constructor:
// correct the constructor pointer because it points to Person
Student.prototype.constructor = Student;
这有什么重要目的吗?省略它可以吗?
它并不总是必要的,但它确实有它的用途。假设我们想在基Person
类上创建一个复制方法。像这样:
// 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);
现在当我们创建一个新的Student
并复制它时会发生什么?
var student1 = new Student("trinth");
console.log(student1.copy() instanceof Student); // => false
该副本不是 的实例Student
。这是因为(没有显式检查),我们无法Student
从“基”类返回副本。我们只能返回一个Person
. 但是,如果我们重置了构造函数:
// correct the constructor pointer because it points to Person
Student.prototype.constructor = Student;
...然后一切都按预期进行:
var student1 = new Student("trinth");
console.log(student1.copy() instanceof Student); // => true
这有什么重要目的吗?
是和否。
在 ES5 和更早的版本中,JavaScript 本身没有任何用途constructor
。它定义了函数prototype
属性上的默认对象将拥有它,并且它将引用回函数,就是这样。规范中没有任何其他内容提及它。
这在 ES2015 (ES6) 中发生了变化,它开始在继承层次结构中使用它。例如,在构建要返回的新Promise时Promise#then
使用constructor
您调用它的Promise的属性(通过SpeciesConstructor)。它还涉及子类型数组(通过ArraySpeciesCreate)。
在语言本身之外,有时人们会在尝试构建通用的“克隆”函数时使用它,或者只是在他们想要引用他们认为是对象的构造函数时一般会使用它。我的经验是很少使用它,但有时人们确实会使用它。
省略它可以吗?
默认情况下它就在那里,您只需要在替换函数prototype
属性上的对象时将其放回原处:
Student.prototype = Object.create(Person.prototype);
如果你不这样做:
Student.prototype.constructor = Student;
...然后Student.prototype.constructor
继承Person.prototype
其中(大概)有constructor = Person
. 所以这是误导。当然,如果您要对使用它的东西(如Promise
或Array
)进行子类化而不使用class
¹(它会为您处理此问题),您需要确保正确设置它。所以基本上:这是一个好主意。
如果您的代码(或您使用的库代码)中没有任何内容使用它,那也没关系。我一直确保它连接正确。
当然,使用 ES2015(又名 ES6)的class
关键字,大多数时候我们会使用它,我们不必再使用它了,因为它会在我们使用时为我们处理
class Student extends Person {
}
¹ “...如果您要对使用它的东西(如Promise
或Array
)进行子类化而不使用class
...” ——这样做是可能的,但这真的很痛苦(而且有点傻)。你必须使用Reflect.construct
.
TLDR;不是非常必要,但从长远来看可能会有所帮助,而且这样做更准确。
注意:由于我之前的答案写得令人困惑,并且在急于回答时遗漏了一些错误,因此进行了大量编辑。感谢那些指出一些严重错误的人。
基本上,它是在 Javascript 中正确连接子类。当我们创建子类时,我们必须做一些奇怪的事情来确保原型委托正确工作,包括覆盖一个prototype
对象。覆盖prototype
对象包括constructor
,因此我们需要修复引用。
让我们快速了解一下 ES5 中的“类”是如何工作的。
假设您有一个构造函数及其原型:
//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',
}
当您调用构造函数进行实例化时,请说Adam
:
// instantiate using the 'new' keyword
var adam = new Person('Adam', 19);
new
用 'Person' 调用的关键字基本上会用几行额外的代码运行 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
* }
*/
如果我们console.log(adam.species)
,查找将在adam
实例处失败,并查找到它的原型链.prototype
,它是Person.prototype
- 并且Person.prototype
具有一个.species
属性,因此查找将在 处成功Person.prototype
。然后它会记录'human'
。
在这里,Person.prototype.constructor
将正确指向Person
。
所以现在是有趣的部分,即所谓的“子类化”。如果我们要创建一个Student
类,即该类的子Person
类并进行一些额外更改,我们需要确保Student.prototype.constructor
指向 Student 的准确性。
它本身不会这样做。子类化时,代码如下所示:
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: {}
在new Student()
这里调用将返回一个具有我们想要的所有属性的对象。在这里,如果我们检查eve instanceof Person
,它将返回false
。如果我们尝试访问eve.species
,它将返回undefined
。
换句话说,我们需要连接委托,以便eve instanceof Person
返回 true 并且Student
委托的实例正确地为Student.prototype
,然后Person.prototype
。
但是既然我们用new
关键字调用它,还记得那个调用添加了什么吗?它会调用Object.create(Student.prototype)
,这就是我们在Student
和之间建立委托关系的方式Student.prototype
。请注意,现在,Student.prototype
是空的。因此,查找.species
的实例Student
将失败,因为它仅 委托给Student.prototype
,并且该.species
属性不存在于Student.prototype
。
当我们确实分配Student.prototype
给 时Object.create(Person.prototype)
,Student.prototype
它本身然后委托给Person.prototype
,并且查找eve.species
将human
如我们预期的那样返回。大概我们希望它继承自 Student.prototype AND Person.prototype。所以我们需要解决所有这些问题。
/* 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);
现在委托工作了,但我们Student.prototype
用 的覆盖了Person.prototype
。因此,如果我们调用Student.prototype.constructor
,它将指向Person
而不是Student
。这就是为什么我们需要修复它。
// 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
在 ES5 中,我们的constructor
属性是一个引用,它引用了我们为了成为“构造函数”而编写的函数。除了new
关键字为我们提供的内容之外,构造函数在其他方面是一个“普通”函数。
在 ES6 中,constructor
现在已经内置在我们编写类的方式中——就像在我们声明一个类时作为方法提供的一样。这只是语法糖,但它确实为我们提供了一些便利,例如super
在我们扩展现有类时访问 a 。所以我们会像这样写上面的代码:
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;
}
}
我不同意。没有必要设置原型。使用完全相同的代码,但删除prototype.constructor 行。有什么变化吗?不。现在,进行以下更改:
Person = function () {
this.favoriteColor = 'black';
}
Student = function () {
Person.call(this);
this.favoriteColor = 'blue';
}
并在测试代码的末尾......
alert(student1.favoriteColor);
颜色将是蓝色。
根据我的经验,对prototype.constructor 的更改并没有太大作用,除非您正在做非常具体、非常复杂的事情,而这些事情无论如何都可能不是很好的做法:)
编辑:在网上闲逛了一下并进行了一些实验后,看起来人们设置了构造函数,使其“看起来”像用“新”构造的东西。我想我会争辩说,问题在于 javascript 是一种原型语言 - 没有继承这样的东西。但是大多数程序员都具有将继承作为“方式”的编程背景。所以我们想出了各种各样的方法来尝试使这种原型语言成为“经典”语言……例如扩展“类”。真的,在他们给出的例子中,一个新学生是一个人——它不是从另一个学生“延伸”出来的……学生就是这个人,无论这个人是什么,学生也是如此。扩展学生,不管你
克罗克福德有点疯狂和过分热情,但认真阅读他写的一些东西......它会让你对这些东西有非常不同的看法。
这有一个巨大的陷阱,如果你写
Student.prototype.constructor = Student;
但是如果有一个老师的原型也是 Person 而你写的
Teacher.prototype.constructor = Teacher;
那么学生构造函数现在是老师!
编辑:您可以通过确保使用 Object.create 创建的 Person 类的新实例设置 Student 和 Teacher 原型来避免这种情况,如 Mozilla 示例中所示。
Student.prototype = Object.create(Person.prototype);
Teacher.prototype = Object.create(Person.prototype);