为什么有必要设置原型构造函数?

tri*_*nth 284 javascript oop inheritance

MDN文章面向对象的Javascript简介中关于继承部分中,我注意到他们设置了prototype.constructor:

// correct the constructor pointer because it points to Person
Student.prototype.constructor = Student;  
Run Code Online (Sandbox Code Playgroud)

这有什么重要意义吗?省略它可以吗?

Way*_*ett 257

它并不总是必要的,但确实有它的用途.假设我们想在基Person类上创建一个copy方法.像这样:

// 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);
Run Code Online (Sandbox Code Playgroud)

现在当我们创建一个新的Student并复制它时会发生什么?

var student1 = new Student("trinth");  
console.log(student1.copy() instanceof Student); // => false
Run Code Online (Sandbox Code Playgroud)

副本不是.的实例Student.这是因为(没有明确的检查),我们无法Student从"基础"类返回副本.我们只能退货了Person.但是,如果我们重置了构造函数:

// correct the constructor pointer because it points to Person  
Student.prototype.constructor = Student;
Run Code Online (Sandbox Code Playgroud)

...然后一切都按预期工作:

var student1 = new Student("trinth");  
console.log(student1.copy() instanceof Student); // => true
Run Code Online (Sandbox Code Playgroud)

  • @ Pumbaa80 - 我明白你的观点,但引擎自动初始化`constructor`的事实意味着它*在JS中具有特殊含义,几乎就是定义. (56认同)
  • 注意:`constructor`属性在JS中没有任何特殊含义,所以你不妨称之为`bananashake`.唯一的区别是每当你声明一个函数`f`时,引擎会自动初始化`f.prototype`上的`constructor`.但是,它可以随时被覆盖. (33认同)
  • 我只是想澄清你说的行为之所以有效,是因为你使用`return new this.constructor(this.name);`而不是`return new Person(this.name);`.由于`this.constructor`是`Student`函数(因为你用`Student.prototype.constructor = Student;`设置它),`copy`函数最终调用`Student`函数.我不确定你对'//同样糟糕'评论的意图是什么. (12认同)
  • @lwburk你是什么意思"//一样糟糕"? (11认同)
  • 我想我明白了.但是,如果`Student`构造函数添加了一个额外的参数,如:`Student(name,id)`,该怎么办?那么我们是否必须覆盖`copy`函数,从其中调用`Person`版本,然后还复制额外的`id`属性? (6认同)
  • 在TJ Crowder的回答中提到了一个补充这个答案的观点.当你定义`Student`函数时,它的`prototype.constructor`*是*`Student`.但是,当调用`Student.prototype = Object.create(Person.prototype);`时,负面影响是`Student.prototype.constructor`属性设置为`Person`.因此,通过将prototype.constructor设置回"Student",可以*固定*. (5认同)
  • 是的,我对此并不是很清楚.我试图说你不能将一个`constructor`分配给一个"class"来改变`new`的行为,但是你必须反过来这样做:给函数分配一个`prototype` .通过设置`constructor`来获得循环引用可能是个好主意,但是当两个函数指向同一个`prototype`时失败. (2认同)
  • @CEGRD - 我的意思是使用该方法也将返回一个`Person`对象,而我们应该期待更具体的`Student` (2认同)
  • 好吧,就像那样.我可能只是创建了一个全新的`copy`方法来复制这两个属性.我认为没有必要调用"父"方法. (2认同)
  • 您可以在复制方法中返回Object.create(this) (2认同)

T.J*_*der 72

这有什么重要意义吗?

是的,不是.

在ES5和更早版本中,JavaScript本身并没有constructor用于任何东西.它定义了函数prototype属性上的默认对象将拥有它,并且它将引用回函数,就是这样.规范中没有任何其他内容可以引用它.

这在ES2015(ES6)中发生了变化,它开始在继承层次结构中使用它.例如,在构建要返回的新承诺时,Promise#then使用constructor您调用它的promise(通过SpeciesConstructor)的属性.它还涉及子类型数组(通过ArraySpeciesCreate).

在语言本身之外,有时人们会在尝试构建通用"克隆"函数时使用它,或者只是在他们想要引用他们认为是对象的构造函数时.我的经验是使用它很少见,但有时候人们会使用它.

省略它可以吗?

默认情况下,您只需要在替换函数prototype属性上的对象时将其放回:

Student.prototype = Object.create(Person.prototype);
Run Code Online (Sandbox Code Playgroud)

如果你不这样做:

Student.prototype.constructor = Student;
Run Code Online (Sandbox Code Playgroud)

......然后Student.prototype.constructor继承Person.prototype(大概)constructor = Person.所以这是误导.当然,如果你是使用它的子类(如PromiseArray)并且不使用class¹(它为你处理这个)的子类,你需要确保正确设置它.所以基本上:这是一个好主意.

如果您的代码(或您使用的库代码)中没有任何内容使用它,那也没关系.我总是确保它正确接线.

当然,使用ES2015(又名ES6)的class关键字,我们大部分时间都会使用它,我们不再需要了,因为当我们这样做时它会被我们处理

class Student extends Person {
}
Run Code Online (Sandbox Code Playgroud)

¹ "......如果你是使用它的子类(如Promise或者Array)而不是使用class......"  - 这可能会这样做,但这真的很痛苦(而且有点傻).你必须使用Reflect.construct.


bth*_*man 12

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',
}
Run Code Online (Sandbox Code Playgroud)

当你调用构造函数来实例化时,说Adam:

// instantiate using the 'new' keyword
var adam = new Person('Adam', 19);
Run Code Online (Sandbox Code Playgroud)

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
 * }
 */
Run Code Online (Sandbox Code Playgroud)

如果我们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: {}
Run Code Online (Sandbox Code Playgroud)

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.prototypeObject.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);
Run Code Online (Sandbox Code Playgroud)

现在代表团工作,但我们覆盖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
Run Code Online (Sandbox Code Playgroud)

在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;
   }
}
Run Code Online (Sandbox Code Playgroud)


Ste*_*hen 10

我不同意.没有必要设置原型.采用完全相同的代码,但删除prototype.constructor行.有什么变化吗?不.现在,进行以下更改:

Person = function () {
    this.favoriteColor = 'black';
}

Student = function () {
    Person.call(this);
    this.favoriteColor = 'blue';
}
Run Code Online (Sandbox Code Playgroud)

并在测试代码结束时......

alert(student1.favoriteColor);
Run Code Online (Sandbox Code Playgroud)

颜色为蓝色.

根据我的经验,对prototype.constructor的更改没有太大作用,除非你做的非常具体,非常复杂的事情,可能不是很好的做法:)

编辑:在网上挖了一下并做了一些实验之后,看起来人们设置了构造函数,使它看起来像是用'new'构造的东西.我想我会争辩说这个问题是javascript是一种原型语言 - 没有继承权这样的东西.但是大多数程序员都来自编程的背景,这种编程将继承作为"方式".因此,我们想出各种各样的东西来尝试将这种原型语言变成"经典"语言......例如扩展"类".实际上,在他们给出的例子中,一个新学生就是一个人 - 它不是从另一个学生那里"延伸"的......学生就是关于这个人的,而无论学生是谁,也是如此.扩展学生,无论您扩展什么,都是学生的核心,但可以根据您的需求进行定制.

克罗克福德有点疯狂和过分热心,但对他写的一些东西做了一些认真的阅读......它会让你以非常不同的方式看待这些东西.

  • 这不会继承原型链. (8认同)
  • 你错过了继承原型的代码.欢迎来到互联网. (7认同)

小智 9

如果你写的话,这有很大的陷阱

Student.prototype.constructor = Student;
Run Code Online (Sandbox Code Playgroud)

但是如果有一位老师的原型也是人,你就写了

Teacher.prototype.constructor = Teacher;
Run Code Online (Sandbox Code Playgroud)

然后学生构造函数现在是老师!

编辑:您可以通过确保使用Object.create创建的Person类的新实例来设置Student和Teacher原型来避免这种情况,如Mozilla示例中所示.

Student.prototype = Object.create(Person.prototype);
Teacher.prototype = Object.create(Person.prototype);
Run Code Online (Sandbox Code Playgroud)

  • @AndréNeves我发现这个答案很有帮助.在生成问题的MDN文章中使用了`Object.create(...)`,但在问题本身中却没有.我相信很多人都不会点击. (3认同)
  • `Student.prototype = Object.create(...)` 在这个问题中是假设的。这个答案只会增加可能的混乱。 (2认同)

小智 5

到目前为止,仍存在困惑.

按照原始示例,因为您有一个现有对象student1:

var student1 = new Student("Janet", "Applied Physics");
Run Code Online (Sandbox Code Playgroud)

假设你不想知道如何student1创建,你只需要像它这样的另一个对象,你可以使用student1like 的构造函数属性:

var student2 = new student1.constructor("Mark", "Object-Oriented JavaScript");
Run Code Online (Sandbox Code Playgroud)

Student如果未设置构造函数属性,则无法获取属性.相反,它会创建一个Person对象.