Javascript 为什么需要设置原型构造函数?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/8453887/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-24 06:03:59  来源:igfitidea点击:

Why is it necessary to set the prototype constructor?

javascriptoopinheritance

提问by trinth

In the section about inheritance in the MDN article Introduction to Object Oriented Javascript, I noticed they set the prototype.constructor:

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

// correct the constructor pointer because it points to Person
Student.prototype.constructor = Student;  

Does this serve any important purpose? Is it okay to omit it?

这有什么重要目的吗?省略它可以吗?

采纳答案by Wayne

It's not always necessary, but it does have its uses. Suppose we wanted to make a copy method on the base Personclass. Like this:

它并不总是必要的,但它确实有它的用途。假设我们想在基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);

Now what happens when we create a new Studentand copy it?

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

var student1 = new Student("trinth");  
console.log(student1.copy() instanceof Student); // => false

The copy is not an instance of Student. This is because (without explicit checks), we'd have no way to return a Studentcopy from the "base" class. We can only return a Person. However, if we had reset the constructor:

该副本不是 的实例Student。这是因为(没有显式检查),我们无法Student从“基”类返回副本。我们只能返回一个Person. 但是,如果我们重置了构造函数:

// correct the constructor pointer because it points to Person  
Student.prototype.constructor = Student;

...then everything works as expected:

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

var student1 = new Student("trinth");  
console.log(student1.copy() instanceof Student); // => true

回答by T.J. Crowder

Does this serve any important purpose?

这有什么重要目的吗?

Yes and no.

是和否。

In ES5 and earlier, JavaScript itself didn't use constructorfor anything. It defined that the default object on a function's prototypeproperty would have it and that it would refer back to the function, and that was it. Nothing else in the specification referred to it at all.

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

That changed in ES2015 (ES6), which started using it in relation to inheritance hierarchies. For instance, Promise#thenuses the constructorproperty of the promise you call it on (via SpeciesConstructor) when building the new promise to return. It's also involved in subtyping arrays (via ArraySpeciesCreate).

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

Outside of the language itself, sometimes people would use it when trying to build generic "clone" functions or just generally when they wanted to refer to what they believed would be the object's constructor function. My experience is that using it is rare, but sometimes people do use it.

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

Is it okay to omit it?

省略它可以吗?

It's there by default, you only need to put it back when you replacethe object on a function's prototypeproperty:

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

Student.prototype = Object.create(Person.prototype);

If you don't do this:

如果你不这样做:

Student.prototype.constructor = Student;

...then Student.prototype.constructorinherits from Person.prototypewhich (presumably) has constructor = Person. So it's misleading. And of course, if you're subclassing something that uses it (like Promiseor Array) and not using class1 (which handles this for you), you'll want to make sure you set it correctly. So basically: It's a good idea.

...然后Student.prototype.constructor继承Person.prototypewhich (大概) has constructor = Person. 所以这是误导。当然,如果您要对使用它的东西(如PromiseArray)进行子类化而不使用class1(它会为您处理),您需要确保正确设置它。所以基本上:这是一个好主意。

It's okay if nothing in your code (or library code you use) uses it. I've always ensured it was correctly wired up.

如果您的代码(或您使用的库代码)中没有任何内容使用它,那也没关系。我一直确保它正确连接。

Of course, with ES2015 (aka ES6)'s classkeyword, most of the time we would have used it, we don't have to anymore, because it's handled for us when we do

当然,使用 ES2015(又名 ES6)的class关键字,大多数时候我们会使用它,我们不必再使用它了,因为它会在我们使用时为我们处理

class Student extends Person {
}


1 "...if you're subclassing something that uses it (like Promiseor Array) and not using class..." — It's possibleto do that, but it's a real pain (and a bit silly). You have to use Reflect.construct.

1 “...如果你对使用它的东西(如PromiseArray)进行子类化而不使用class...” - 这样做是可能的,但这真的很痛苦(而且有点愚蠢)。你必须使用Reflect.construct.

回答by bthehuman

TLDR; Not super necessary, but will probably help in the long run, and it is more accurate to do so.

TLDR;不是非常必要,但从长远来看可能会有所帮助,而且这样做更准确。

NOTE: Much edited as my previous answer was confusingly written and had some errors that I missed in my rush to answer. Thanks to those who pointed out some egregious errors.

注意:由于我之前的答案写得令人困惑,并且在急于回答时遗漏了一些错误,因此进行了大量编辑。感谢那些指出一些严重错误的人。

Basically, it's to wire subclassing up correctly in Javascript. When we subclass, we have to do some funky things to make sure that the prototypal delegation works correctly, including overwriting a prototypeobject. Overwriting a prototypeobject includes the constructor, so we then need to fix the reference.

基本上,它是在 Javascript 中正确连接子类。当我们创建子类时,我们必须做一些奇怪的事情来确保原型委托正常工作,包括覆盖一个prototype对象。覆盖prototype对象包括constructor,因此我们需要修复引用。

Let's quickly go through how 'classes' in ES5 work.

让我们快速浏览一下 ES5 中的“类”是如何工作的。

Let's say you have a constructor function and its prototype:

假设您有一个构造函数及其原型:

//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',
}

When you call the constructor to instantiate, say Adam:

当您调用构造函数进行实例化时,请说Adam

// instantiate using the 'new' keyword
var adam = new Person('Adam', 19);

The newkeyword invoked with 'Person' basically will run the Person constructor with a few additional lines of code:

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
 * }
 */

If we console.log(adam.species), the lookup will fail at the adaminstance, and look up the prototypal chain to its .prototype, which is Person.prototype- and Person.prototypehasa .speciesproperty, so the lookup will succeed at Person.prototype. It will then log 'human'.

如果我们console.log(adam.species),查找将在adam实例处失败,并查找到它的原型链.prototype,它是Person.prototype- 并且Person.prototype具有一个.species属性,因此查找将在 处成功Person.prototype。然后它会记录'human'

Here, the Person.prototype.constructorwill correctly point to Person.

在这里,Person.prototype.constructor将正确指向Person

So now the interesting part, the so-called 'subclassing'. If we want to create a Studentclass, that is a subclass of the Personclass with some additional changes, we'll need to make sure that the Student.prototype.constructorpoints to Student for accuracy.

所以现在是有趣的部分,即所谓的“子类化”。如果我们要创建一个Student类,即该类的子Person类并进行一些额外更改,我们需要确保Student.prototype.constructor指向 Student 的准确性。

It doesn't do this by itself. When you subclass, the code looks like this:

它本身不会这样做。子类化时,代码如下所示:

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: {}

Calling new Student()here would return an object with all of the properties we want. Here, if we check eve instanceof Person, it would return false. If we try to access eve.species, it would return undefined.

new Student()这里调用将返回一个具有我们想要的所有属性的对象。在这里,如果我们检查eve instanceof Person,它将返回false。如果我们尝试访问eve.species,它将返回undefined

In other words, we need to wire up the delegation so that eve instanceof Personreturns true and so that instances of Studentdelegate correctly to Student.prototype, and then Person.prototype.

换句话说,我们需要连接委托,以便eve instanceof Person返回 true 并且Student委托的实例正确地为Student.prototype,然后Person.prototype

BUT since we're calling it with the newkeyword, remember what that invocation adds? It would call Object.create(Student.prototype), which is how we set up that delegational relationship between Studentand Student.prototype. Note that right now, Student.prototypeis empty. So looking up .speciesan instance of Studentwould fail as it delegates to onlyStudent.prototype, and the .speciesproperty doesn't exist on Student.prototype.

但是既然我们用new关键字调用它,还记得那个调用添加了什么吗?它会调用Object.create(Student.prototype),这就是我们在Student和之间建立委托关系的方式Student.prototype。请注意,现在,Student.prototype是空的。因此,查找.species的实例Student将失败,因为它委托给Student.prototype,并且该.species属性不存在于Student.prototype

When we do assign Student.prototypeto Object.create(Person.prototype), Student.prototypeitself then delegates to Person.prototype, and looking up eve.specieswill return humanas we expect. Presumably we would want it to inherit from Student.prototype AND Person.prototype. So we need to fix all of that.

当我们分配Student.prototype给 时Object.create(Person.prototype)Student.prototype它本身然后委托给Person.prototype,并且查找eve.specieshuman如我们预期的那样返回。大概我们希望它从 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);

Now the delegation works, but we're overwriting Student.prototypewith an of Person.prototype. So if we call Student.prototype.constructor, it would point to Personinstead of Student. Thisis why we need to fix it.

现在委托工作了,但我们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

In ES5, our constructorproperty is a reference that refers to a function that we've written with the intent to be a 'constructor'. Aside from what the newkeyword gives us, the constructor is otherwise a 'plain' function.

在 ES5 中,我们的constructor属性是一个引用,它引用了我们为了成为“构造函数”而编写的函数。除了new关键字给我们的东西之外,构造函数是一个“普通”函数。

In ES6, the constructoris now built into the way we write classes - as in, it's provided as a method when we declare a class. This is simply syntactic sugar but it does accord us some conveniences like access to a superwhen we are extending an existing class. So we would write the above code like this:

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

回答by Stephen

I'd disagree. It isn't necessary to set the prototype. Take that exact same code but remove the prototype.constructor line. Does anything change? No. Now, make the following changes:

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

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

Student = function () {
    Person.call(this);
    this.favoriteColor = 'blue';
}

and at the end of the test code...

并在测试代码的末尾......

alert(student1.favoriteColor);

The color will be blue.

颜色将是蓝色。

A change to the prototype.constructor, in my experience, doesn't do much unless you're doing very specific, very complicated things that probably aren't good practice anyway :)

根据我的经验,对prototype.constructor 的更改并没有太大作用,除非您正在做非常具体、非常复杂的事情,而这些事情无论如何都可能不是很好的做法:)

Edit: After poking around the web for a bit and doing some experimentation, it looks like people set the constructor so that it 'looks' like the thing that is being constructed with 'new'. I guess I would argue that the problem with this is that javascript is a prototype language - there is no such thing as inheritence. But most programmers come from a background of programming that pushes inheritence as 'the way'. So we come up with all sorts of things to try and make this prototypical language a 'classic' language.. such as extending 'classes'. Really, in the example they gave, a new student is a person - it isn't 'extending' from another student.. the student is all about the person, and whatever the person is the student is as well. Extend the student, and whatever you've extended is a student at heart, but is customized to fit your needs.

编辑:在网上浏览了一下并进行了一些实验后,看起来人们设置了构造函数,使其“看起来”像使用“new”构造的东西。我想我会争辩说,问题在于javascript是一种原型语言——没有继承这样的东西。但是大多数程序员都具有将继承作为“方式”的编程背景。因此,我们想出了各种方法来尝试使这种原型语言成为“经典”语言……例如扩展“类”。真的,在他们给出的例子中,一个新学生是一个人——它不是从另一个学生“延伸”出来的……学生就是这个人,无论这个人是什么,学生也是如此。扩展学生,无论你'

Crockford is a bit crazy and overzealous, but do some serious reading on some of the stuff that he's written.. it'll make you look at this stuff very differently.

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

回答by James D

This has the huge pitfall that if you wrote

这有一个巨大的陷阱,如果你写

Student.prototype.constructor = Student;

but then if there was a Teacher whose prototype was also Person and you wrote

但是如果有一个老师的原型也是 Person 而你写的

Teacher.prototype.constructor = Teacher;

then the Student constructor is now Teacher!

那么学生构造函数现在是老师!

Edit: You can avoid this by ensuring that you had set the Student and Teacher prototypes using new instances of the Person class created using Object.create, as in the Mozilla example.

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

Student.prototype = Object.create(Person.prototype);
Teacher.prototype = Object.create(Person.prototype);

回答by Mahavir

So far confusion is still there.

到目前为止,混乱仍然存在。

Following the original example, as you have an existing object student1as:

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

var student1 = new Student("Janet", "Applied Physics");

Suppose you don't want to know how student1is created, you just want another object like it, you can use the constructor property of student1like:

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

var student2 = new student1.constructor("Mark", "Object-Oriented JavaScript");

Here it will fail to get the properties from Studentif the constructor property is not set. Rather it will create a Personobject.

Student如果未设置构造函数属性,则此处将无法从中获取属性。相反,它将创建一个Person对象。

回答by user3877965

Got a nice code example of why it is really necessary to set the prototype constructor..

有一个很好的代码示例,说明为什么真的有必要设置原型构造函数..

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 ???? 
*/

回答by ucsarge

No need for sugared function 'classes' or using 'New' these days. Use object literals.

这些天不需要加糖的函数“类”或使用“新”。使用对象字面量。

The Object prototype is already a 'class'. When you define an object literal, it is already an instance of the prototype Object. These can also act as another object's prototype, etc.

Object 原型已经是一个“类”。当你定义一个对象字面量时,它已经是原型对象的一个​​实例。这些也可以充当另一个对象的原型等。

const Person = {
  name: '[Person.name]',
  greeting: function() {
    console.log( `My name is ${ this.name || '[Name not assigned]' }` );
  }
};
// Person.greeting = function() {...} // or define outside the obj if you must

// Object.create version
const john = Object.create( Person );
john.name = 'John';
console.log( john.name ); // John
john.greeting(); // My name is John 
// Define new greeting method
john.greeting = function() {
    console.log( `Hi, my name is ${ this.name }` )
};
john.greeting(); // Hi, my name is John

// Object.assign version
const jane = Object.assign( Person, { name: 'Jane' } );
console.log( jane.name ); // Jane
// Original greeting
jane.greeting(); // My name is Jane 

// Original Person obj is unaffected
console.log( Person.name ); // [Person.name]
console.log( Person.greeting() ); // My name is [Person.name]

This is worth a read:

这值得一读

Class-based object-oriented languages, such as Java and C++, are founded on the concept of two distinct entities: classes and instances.

...

A prototype-based language, such as JavaScript, does not make this distinction: it simply has objects. A prototype-based language has the notion of a prototypical object, an object used as a template from which to get the initial properties for a new object. Any object can specify its own properties, either when you create it or at run time. In addition, any object can be associated as the prototype for another object, allowing the second object to share the first object's properties

基于类的面向对象语言,例如 Java 和 C++,建立在两个不同实体的概念之上:类和实例。

...

基于原型的语言,例如 JavaScript,没有这种区别:它只有对象。基于原型的语言具有原型对象的概念,该对象用作模板,从中获取新对象的初始属性。任何对象都可以在创建时或运行时指定自己的属性。此外,任何对象都可以关联为另一个对象的原型,从而允许第二个对象共享第一个对象的属性

回答by Paul Sweatte

It is necessary when you need an alternative to toStringwithout monkeypatching:

当您需要toString不使用monkeypatching的替代方案时,这是必要的:

//Local
foo = [];
foo.toUpperCase = String(foo).toUpperCase;
foo.push("a");
foo.toUpperCase();

//Global
foo = [];
window.toUpperCase = function (obj) {return String(obj).toUpperCase();}
foo.push("a");
toUpperCase(foo);

//Prototype
foo = [];
Array.prototype.toUpperCase = String.prototype.toUpperCase;
foo.push("a");
foo.toUpperCase();

//toString alternative via Prototype constructor
foo = [];
Array.prototype.constructor = String.prototype.toUpperCase;
foo.push("a,b");
foo.constructor();

//toString override
var foo = [];
foo.push("a");
var bar = String(foo);
foo.toString = function() { return bar.toUpperCase(); }
foo.toString();

//Object prototype as a function
Math.prototype = function(char){return Math.prototype[char]};
Math.prototype.constructor = function() 
  {
  var i = 0, unicode = {}, zero_padding = "0000", max = 9999;
  
  while (i < max) 
    {
    Math.prototype[String.fromCharCode(parseInt(i, 16))] = ("u" + zero_padding + i).substr(-4);

    i = i + 1;
    }    
  }

Math.prototype.constructor();
console.log(Math.prototype("a") );
console.log(Math.prototype["a"] );
console.log(Math.prototype("a") === Math.prototype["a"]);

回答by Hitesh Kumar

Here's one example from MDN which I found very helpful to understand its uses.

这是来自 MDN 的一个例子,我发现它对理解它的用途非常有帮助。

In JavaScript, we have async functionswhich returns AsyncFunctionobject. AsyncFunctionis not a global object but one may retrieve it by using constructorproperty and utilize it.

在 JavaScript 中,我们有async functionswhich 返回AsyncFunction对象。AsyncFunction不是一个全局对象,但可以通过使用constructor属性来检索它并利用它。

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