在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;
这有什么重要的目的吗?可以省略吗?
当前回答
这有什么重要的目的吗?
是也不是。
在ES5和更早的版本中,JavaScript本身并不使用构造函数。它定义了函数原型属性的默认对象将拥有它,并且它将引用回函数,就是这样。规范中没有其他内容提到它。
这种情况在ES2015 (ES6)中改变了,它开始在继承层次结构中使用它。例如,Promise#然后在构建要返回的新Promise时使用调用它的Promise的构造函数属性(通过SpeciesConstructor)。它还涉及数组的子类型(通过ArraySpeciesCreate)。
在语言本身之外,有时人们会在试图构建通用的“克隆”函数时使用它,或者只是在他们想要引用他们认为是对象的构造函数时使用它。我的经验是,很少有人使用它,但有时确实有人使用它。
可以省略吗?
它默认存在,你只需要在替换一个函数的prototype属性的对象时把它放回去:
Student.prototype = Object.create(Person.prototype);
如果你不这样做:
Student.prototype.constructor = Student;
...那么Student.prototype.constructor继承自Person。原型(假设)有构造函数= Person。所以这是误导。当然,如果你要子类化一些使用它的东西(比如Promise或Array),而不是使用类¹(它为你处理这个),你会想要确保你正确地设置它。所以基本上,这是个好主意。
如果您的代码(或您使用的库代码)中没有使用它,那也没关系。我一直确保它是正确连接的。
当然,对于ES2015(又名ES6)的class关键字,大多数时候我们会使用它,我们不再需要它了,因为当我们使用它时它已经为我们处理了
class Student extends Person {
}
¹”…如果你正在子类化一些使用它的东西(比如Promise或Array),而不使用class……”-这是可以做到的,但这真的很痛苦(而且有点傻)。你必须使用reflect。construct。
其他回答
TLDR;不是非常必要,但从长远来看可能会有所帮助,这样做更准确。
注意:我之前的回答被编辑了很多,写得令人困惑,有一些错误,我在匆忙回答时漏掉了。感谢那些指出了一些严重错误的人。
基本上,它是在Javascript中正确地连接子类。当我们子类化时,我们必须做一些奇怪的事情来确保原型委托正确工作,包括覆盖原型对象。重写原型对象包括构造函数,因此我们需要修复引用。
让我们快速浏览一下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);
使用'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
* }
*/
如果我们console.log(adam.species),查找将在adam实例中失败,并查找原型链到它的.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()将返回一个具有我们想要的所有属性的对象。在这里,如果我们检查einstanceof Person,它将返回false。如果我们试图接近夏娃。物种,它将返回undefined。
换句话说,我们需要连接委托,以便eve instanceof Person返回true,以便Student的实例正确地委托给Student。prototype,然后person。prototype。
但是,由于我们使用new关键字调用它,还记得调用添加了什么吗?它会调用object。create(Student。prototype)这就是我们在Student和Student。prototype之间建立委托关系的方式。现在请注意,学生。原型是空的。因此,查找.species Student实例将失败,因为它只委托给Student。在Student.prototype中不存在。species属性。
当我们分配Student时。prototype to Object.create(Person.prototype), Student。原型本身然后委托给Person。原型,和向上看的夏娃。物种会像我们期望的那样回归人类。假设我们希望它从Student继承。原型和人。所以我们需要解决这些问题。
/* 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。Person.prototype的原型。如果我们调用student。prototype。构造函数时,它将指向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中,我们的构造函数属性是一个引用,它指向一个我们写的函数,目的是成为一个“构造函数”。除了new关键字给了我们什么,构造函数在其他方面是一个“普通”函数。
在ES6中,构造函数现在被内置到我们编写类的方式中——也就是说,当我们声明一个类时,它作为一个方法提供。这只是语法上的“糖”,但它确实给我们提供了一些方便,比如在扩展现有类时访问super。所以我们可以这样写上面的代码:
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;
}
}
它并不总是必要的,但它确实有它的用途。假设我们想在基类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
到目前为止,困惑仍然存在。
按照原来的例子,你有一个现有的对象student1 as:
var student1 = new Student("Janet", "Applied Physics");
假设你不想知道student1是如何创建的,你只是想要另一个类似的对象,你可以使用student1的构造函数属性:
var student2 = new student1.constructor("Mark", "Object-Oriented JavaScript");
在这里,如果没有设置构造函数属性,它将无法从Student获取属性。相反,它将创建一个Person对象。
下面是MDN的一个例子,我发现它对理解它的用法很有帮助。
在JavaScript中,我们有返回AsyncFunction对象的async函数。AsyncFunction不是一个全局对象,但可以通过使用构造函数属性检索它并利用它。
function resolveAfter2Seconds(x) {
return new Promise(resolve => {
setTimeout(() => {
resolve(x);
}, 2000);
});
}
// AsyncFunction constructor
var AsyncFunction = Object.getPrototypeOf(async function(){}).constructor
var a = new AsyncFunction('a',
'b',
'return await resolveAfter2Seconds(a) + await resolveAfter2Seconds(b);');
a(10, 20).then(v => {
console.log(v); // prints 30 after 4 seconds
});
这有什么重要的目的吗?
是也不是。
在ES5和更早的版本中,JavaScript本身并不使用构造函数。它定义了函数原型属性的默认对象将拥有它,并且它将引用回函数,就是这样。规范中没有其他内容提到它。
这种情况在ES2015 (ES6)中改变了,它开始在继承层次结构中使用它。例如,Promise#然后在构建要返回的新Promise时使用调用它的Promise的构造函数属性(通过SpeciesConstructor)。它还涉及数组的子类型(通过ArraySpeciesCreate)。
在语言本身之外,有时人们会在试图构建通用的“克隆”函数时使用它,或者只是在他们想要引用他们认为是对象的构造函数时使用它。我的经验是,很少有人使用它,但有时确实有人使用它。
可以省略吗?
它默认存在,你只需要在替换一个函数的prototype属性的对象时把它放回去:
Student.prototype = Object.create(Person.prototype);
如果你不这样做:
Student.prototype.constructor = Student;
...那么Student.prototype.constructor继承自Person。原型(假设)有构造函数= Person。所以这是误导。当然,如果你要子类化一些使用它的东西(比如Promise或Array),而不是使用类¹(它为你处理这个),你会想要确保你正确地设置它。所以基本上,这是个好主意。
如果您的代码(或您使用的库代码)中没有使用它,那也没关系。我一直确保它是正确连接的。
当然,对于ES2015(又名ES6)的class关键字,大多数时候我们会使用它,我们不再需要它了,因为当我们使用它时它已经为我们处理了
class Student extends Person {
}
¹”…如果你正在子类化一些使用它的东西(比如Promise或Array),而不使用class……”-这是可以做到的,但这真的很痛苦(而且有点傻)。你必须使用reflect。construct。