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
Why is it necessary to set the prototype constructor?
提问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 Person
class. 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 Student
and 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 Student
copy 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 constructor
for anything. It defined that the default object on a function's prototype
property 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#then
uses the constructor
property 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 prototype
property:
默认情况下它就在那里,您只需要在替换函数prototype
属性上的对象时将其放回原处:
Student.prototype = Object.create(Person.prototype);
If you don't do this:
如果你不这样做:
Student.prototype.constructor = Student;
...then Student.prototype.constructor
inherits from Person.prototype
which (presumably) has constructor = Person
. So it's misleading. And of course, if you're subclassing something that uses it (like Promise
or Array
) and not using class
1 (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.prototype
which (大概) has constructor = Person
. 所以这是误导。当然,如果您要对使用它的东西(如Promise
或Array
)进行子类化而不使用class
1(它会为您处理),您需要确保正确设置它。所以基本上:这是一个好主意。
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 class
keyword, 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 Promise
or 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 “...如果你对使用它的东西(如Promise
或Array
)进行子类化而不使用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 prototype
object. Overwriting a prototype
object 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 new
keyword 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 adam
instance, and look up the prototypal chain to its .prototype
, which is Person.prototype
- and Person.prototype
hasa .species
property, 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.constructor
will correctly point to Person
.
在这里,Person.prototype.constructor
将正确指向Person
。
So now the interesting part, the so-called 'subclassing'. If we want to create a Student
class, that is a subclass of the Person
class with some additional changes, we'll need to make sure that the Student.prototype.constructor
points 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 Person
returns true and so that instances of Student
delegate 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 new
keyword, remember what that invocation adds? It would call Object.create(Student.prototype)
, which is how we set up that delegational relationship between Student
and Student.prototype
. Note that right now, Student.prototype
is empty. So looking up .species
an instance of Student
would fail as it delegates to onlyStudent.prototype
, and the .species
property 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.prototype
to Object.create(Person.prototype)
, Student.prototype
itself then delegates to Person.prototype
, and looking up eve.species
will return human
as 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.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);
Now the delegation works, but we're overwriting Student.prototype
with an of Person.prototype
. So if we call Student.prototype.constructor
, it would point to Person
instead 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 constructor
property is a reference that refers to a function that we've written with the intent to be a 'constructor'. Aside from what the new
keyword gives us, the constructor is otherwise a 'plain' function.
在 ES5 中,我们的constructor
属性是一个引用,它引用了我们为了成为“构造函数”而编写的函数。除了new
关键字给我们的东西之外,构造函数是一个“普通”函数。
In ES6, the constructor
is 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 super
when 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 student1
as:
按照原始示例,因为您有一个现有对象student1
:
var student1 = new Student("Janet", "Applied Physics");
Suppose you don't want to know how student1
is created, you just want another object like it, you can use the constructor property of student1
like:
假设你不想知道student1
是如何创建的,你只想要另一个类似的对象,你可以使用像这样的构造函数属性student1
:
var student2 = new student1.constructor("Mark", "Object-Oriented JavaScript");
Here it will fail to get the properties from Student
if the constructor property is not set. Rather it will create a Person
object.
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]
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 toString
without 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 functions
which returns AsyncFunctionobject. AsyncFunction
is not a global object but one may retrieve it by using constructor
property and utilize it.
在 JavaScript 中,我们有async functions
which 返回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
});