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);
使用 '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 - 和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);
现在委派工作了,但我们用Person.prototype 覆盖Student.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。所以我们会这样写上面的代码:
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;
}
}