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和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;
}
}