在section about inheritance in the MDN article Introduction to Object Oriented Javascript中,我注意到他们设置了prototype.constructor:
// correct the constructor pointer because it points to Person
Student.prototype.constructor = Student;
这有什么重要意义吗?省略它可以吗?
答案 0 :(得分:244)
并不总是必要的,但确实有它的用途。假设我们想在基类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);
现在,当我们创建一个新的Student
并复制它时会发生什么?
var student1 = new Student("trinth");
console.log(student1.copy() instanceof Student); // => false
副本不是Student
的实例。这是因为(没有明确的检查),我们无法从“base”类返回Student
副本。我们只能返回Person
。但是,如果我们重置了构造函数:
// correct the constructor pointer because it points to Person
Student.prototype.constructor = Student;
...然后一切都按预期工作:
var student1 = new Student("trinth");
console.log(student1.copy() instanceof Student); // => true
答案 1 :(得分:11)
<强> 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
- 和{{1 具有 Person.prototype
属性,因此查找将在.species
处成功。然后它会记录Person.prototype
。
此处,'human'
会正确指向Person.prototype.constructor
。
所以现在是有趣的部分,即所谓的“子类化”。如果我们要创建一个Person
类,这是Student
类的子类并进行一些其他更改,我们需要确保Person
指向Student以确保准确性。
它本身并不是这样做的。子类化时,代码如下所示:
Student.prototype.constructor
在这里调用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
返回true,以便eve instanceof Person
的实例正确委托给Student
,然后Student.prototype
。
但是因为我们使用Person.prototype
关键字调用它,还记得调用添加了什么吗?它会调用new
,这就是我们在Object.create(Student.prototype)
和Student
之间设置委托关系的方式。请注意,目前Student.prototype
为空。因此查找Student.prototype
.species
实例会失败,因为它仅委派 Student
,Student.prototype
属性不存在于.species
{1}}。
当我们将Student.prototype
分配给Student.prototype
,Object.create(Person.prototype)
本身然后委托给Student.prototype
,查找Person.prototype
将返回eve.species
as我们期待。据推测,我们希望它继承自Student.prototype和Person.prototype。所以我们需要解决所有问题。
human
现在代表团工作,但我们用/* 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
在ES5中,我们的// 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
属性是一个引用,它引用了我们编写的函数,意图是'构造函数'。除了constructor
关键字给我们的内容之外,构造函数还是一个“普通”函数。
在ES6中,new
现在构建在我们编写类的方式中 - 就像在我们声明一个类时它作为一个方法提供的。这只是语法糖,但它确实给了我们一些便利,例如当我们扩展现有类时访问constructor
。所以我们会像这样编写上面的代码:
super
答案 2 :(得分:10)
我不同意。没有必要设置原型。采用完全相同的代码,但删除prototype.constructor行。有什么变化吗?不。现在,进行以下更改:
Person = function () {
this.favoriteColor = 'black';
}
Student = function () {
Person.call(this);
this.favoriteColor = 'blue';
}
并在测试代码的末尾......
alert(student1.favoriteColor);
颜色为蓝色。
根据我的经验,对prototype.constructor的更改没有太大作用,除非你做的非常具体,非常复杂的事情可能不是很好的做法:)
编辑: 在网上浏览了一下并进行了一些实验之后,看起来人们设置了构造函数,使它看起来像是用'new'构造的东西。我想我会争辩说这个问题是javascript是一种原型语言 - 没有继承权这样的东西。但大多数程序员都来自编程的背景,这种编程将继承作为“方式”。因此,我们想出各种各样的东西来尝试将这种原型语言变成“经典”语言......例如扩展“类”。实际上,在他们给出的例子中,一个新学生就是一个人 - 它不是从另一个学生那里“延伸”......学生就是关于这个人的,而且无论学生是谁,也是如此。扩展学生,无论您扩展什么,都是学生的核心,但可以根据您的需求进行定制。
Crockford有点疯狂和过分热心,但对他写的一些东西做了一些认真的阅读......它会让你以非常不同的方式看待这些东西。
答案 3 :(得分:9)
如果你写了
,这就有很大的陷阱Student.prototype.constructor = Student;
但是如果有一位老师的原型也是人,你写了
Teacher.prototype.constructor = Teacher;
然后学生构造函数现在是老师!
编辑: 您可以通过确保使用Object.create创建的Person类的新实例来设置Student和Teacher原型来避免这种情况,如Mozilla示例所示。
Student.prototype = Object.create(Person.prototype);
Teacher.prototype = Object.create(Person.prototype);
答案 4 :(得分:5)
到目前为止,混乱仍然存在。
按照原始示例,因为您有一个现有对象student1
:
var student1 = new Student("Janet", "Applied Physics");
假设您不想知道如何创建student1
,您只想要另一个类似的对象,您可以使用student1
的构造函数属性,如:
var student2 = new student1.constructor("Mark", "Object-Oriented JavaScript");
如果未设置构造函数属性,则无法从Student
获取属性。相反,它会创建一个Person
对象。
答案 5 :(得分:2)
有一个很好的代码示例,说明为什么确实需要设置原型构造函数..
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 ????
*/
答案 6 :(得分:1)
这些天不需要加糖功能'类'或使用'新'。使用对象文字。
Object原型已经是一个'类'。定义对象文字时,它已经是原型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]
基于类的面向对象语言,例如Java和C ++ 基于两个不同实体的概念:类和 实例
...
基于原型的语言(例如JavaScript)不能实现这一点 区别:它只是有对象。基于原型的语言有 原型对象的概念,用作模板的对象 哪个获取新对象的初始属性。任何对象都可以 在创建它时或在运行时指定它自己的属性。 此外,任何对象都可以作为另一个对象的原型关联 对象,允许第二个对象共享第一个对象 特性
答案 7 :(得分:1)
当你需要替代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"]);
答案 8 :(得分:0)
是的,这是必要的。当你这样做
Student.prototype = new Person();
Student.prototype.constructor
变为Person
。因此,调用Student()
将返回由Person
创建的对象。如果你那么做
Student.prototype.constructor = Student;
Student.prototype.constructor
重置为Student
。现在当你调用Student()
它执行调用父构造函数Student
的{{1}}时,它会返回正确继承的对象。如果您在调用之前没有重置Parent()
,那么您将获得一个不会在Student.prototype.constructor
中设置任何属性的对象。
答案 9 :(得分:0)
给出简单的构造函数:
function Person(){
this.name = 'test';
}
console.log(Person.prototype.constructor) // function Person(){...}
Person.prototype = { //constructor in this case is Object
sayName: function(){
return this.name;
}
}
var person = new Person();
console.log(person instanceof Person); //true
console.log(person.sayName()); //test
console.log(Person.prototype.constructor) // function Object(){...}
默认情况下(来自规范https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/constructor),所有原型都会自动获得一个名为constructor的属性,该属性指向它所属的函数。 根据构造函数的不同,可能会在原型中添加其他属性和方法,这不是一种非常常见的做法,但仍允许进行扩展。
所以简单回答:我们需要确保prototype.constructor中的值被正确设置,因为规范应该是这样。
我们是否必须始终正确设置此值?它有助于调试并使内部结构与规范保持一致。当我们的API被第三方使用时,我们当然应该这样,但是当代码最终在运行时中执行时,我们就不应该这样做。
答案 10 :(得分:0)
以下是MDN的一个示例,我发现它对理解其用法非常有帮助。
在JavaScript中,我们有async functions
返回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
});
答案 11 :(得分:0)
这是必要的。类继承中的任何类都必须具有自己的构造函数,例如原型继承中那样,这也便于对象构造。但是这个问题是不必要的,需要了解在JavaScript世界中调用函数作为构造函数的作用以及解决对象属性的规则。
以表达式 新<函数名>([参数])
作为构造函数执行函数的效果对象解析属性的规则
基于这些基础机制,语句
答案 12 :(得分:-1)
没有必要。这只是传统的OOP冠军之一,试图将JavaScript的原型继承转化为经典继承。以下唯一的事情
Student.prototype.constructor = Student;
是,你现在有一个当前&#34;构造函数的引用&#34;。
在Wayne的回答中,已经标记为正确,你可能与以下代码完全相同
Person.prototype.copy = function() {
// return new Person(this.name); // just as bad
return new this.constructor(this.name);
};
使用下面的代码(只需用Person替换this.constructor)
Person.prototype.copy = function() {
// return new Person(this.name); // just as bad
return new Person(this.name);
};
感谢上帝,使用ES6经典继承纯粹主义者可以使用语言的本地运算符,如类,扩展和超级,我们不必看到像prototype.constructor更正和父级引用。