在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;
这有什么重要的目的吗?可以省略吗?
当前回答
这有一个巨大的陷阱,如果你写
Student.prototype.constructor = Student;
但如果有一个老师,他的原型也是人,你写
Teacher.prototype.constructor = Teacher;
那么学生构造函数现在是老师!
编辑: 您可以通过确保您已经使用使用Object创建的Person类的新实例设置了Student和Teacher原型来避免这种情况。创建,如Mozilla示例中所示。
Student.prototype = Object.create(Person.prototype);
Teacher.prototype = Object.create(Person.prototype);
其他回答
这有一个巨大的陷阱,如果你写
Student.prototype.constructor = Student;
但如果有一个老师,他的原型也是人,你写
Teacher.prototype.constructor = Teacher;
那么学生构造函数现在是老师!
编辑: 您可以通过确保您已经使用使用Object创建的Person类的新实例设置了Student和Teacher原型来避免这种情况。创建,如Mozilla示例中所示。
Student.prototype = Object.create(Person.prototype);
Teacher.prototype = Object.create(Person.prototype);
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;
}
}
这是必要的。类继承中的任何类都必须有自己的构造函数,在原型继承中也是如此。它也方便对象的构建。但这个问题是不必要的,真正需要理解的是在JavaScript世界中调用函数作为构造函数的效果和解析对象属性的规则。
使用表达式new <函数名>([parameters])将函数作为构造函数执行的效果
创建类型名称为函数名的对象 函数中的内部属性附加到创建的对象 函数的属性原型作为原型自动附加到创建的对象上
对象属性解析规则
不仅要在对象上查找属性,还要在对象的原型、原型的原型上查找属性,以此类推,直到找到具有匹配名称的属性或到达原型链的末尾。
基于这些底层机制,语句<构造函数名称>.prototype. conf。构造函数= <构造函数名称>等于在构造函数体中附加表达式this的效果。构造函数= <构造函数名称>。如果第二次发声,构造函数将在对象上解析;如果第一次发声,构造函数将在对象的原型上解析。
得到了一个很好的代码示例,为什么真的有必要设置原型构造函数..
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 ????
*/
下面是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
});