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

在MDN文章Introduction to Object Oriented Javascript中有关inheritance的部分 ,我注意到他们设置了prototype.constructor:

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

这是否有重要的目的? 可以忽略它吗?

这并不总是必要的,但它确实有其用处。 假设我们想在基类Person类上创build一个复制方法。 喜欢这个:

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

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

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

该副本不是Student一个实例。 这是因为(没有明确的检查),我们没有办法从“基础”类返回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 

这是否有重要的目的?

是和不是。

在ES5及更早版本中,JavaScript本身并没有使用constructor 。 它定义了一个函数prototype属性的默认对象会拥有它,并且它会引用该函数, 就是这样 。 说明书中没有任何内容提及它。

在ES2015(ES6)中有所改变,ES2015开始使用它与inheritance层次结构相关。 例如, Promise#thenconstructor新的承诺返回时使用Promise#thenconstructor属性(通过SpeciesConstructor )。 它也涉及到子types数组(通过ArraySpeciesCreate )。

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

可以忽略它吗?

它在默认情况下是存在的,只需要在函数的prototype属性中replace对象时将其放回原处:

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

如果你不这样做:

 Student.prototype.constructor = Student; 

…然后Student.prototype.constructorPerson.prototypeinheritance(推测)具有constructor = Person 。 所以这是误导。 当然,如果你使用它的东西(如PromiseArray )而不使用Class¹(它会为你处理),那么你需要确保你正确地设置它。 所以基本上:这是一个好主意。

如果你的代码(或者你使用的库代码)没有使用它,那也没关系。 我一直确保它正确连线。

当然,有了ES2015(又名ES6)的关键词,大部分时间我们都可以使用它,我们不必再这样做了,因为当我们做

 class Student extends Person { } 

¹ “…如果你使用它的东西(如PromiseArray )而不使用class …” – 这是可能的 ,但这是一个真正的痛苦(有​​点傻)。 你必须使用Reflect.construct

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

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

并在testing代码的结尾…

 alert(student1.favoriteColor); 

颜色将是蓝色的。

根据我的经验,对prototype.constructor的改变并没有多大的作用,除非你做了非常特殊的,非常复杂的事情,可能不是好的做法:)

编辑:在Web上稍微摸索并做一些实验之后,看起来好像人们设置构造函数,以便它看起来像是用“new”构造的东西。 我想我会争辩说,这个问题是,JavaScript是一个原型语言 – 有没有这样的事情inheritance。 但是大多数程序员都是从程序devise的背景出发,把inheritance作为“方式”。 所以我们想出各种各样的东西来试图把这个原型语言作为一种“经典”的语言,比如扩展“类”。 真的,在他们给的例子中,一个新学生是一个人 – 它不是从另一个学生那里“延伸”的。学生是关于这个人的,关于这个人是什么人也是。 扩展学生,无论你扩展的是一个学生的心,但定制,以满足您的需求。

克罗克福德有点疯狂和过分热心,但是要仔细阅读一些他写的东西。这会让你看起来很不一样。

如果你写的话,这有巨大的缺陷

 Student.prototype.constructor = Student; 

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

 Teacher.prototype.constructor = Teacher; 

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

编辑:您可以通过确保您已经使用Object.create创build的Person类的新实例来设置Student和Teacher原型,如Mozilla示例中所示。

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

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 – 而Person.prototype 具有 .species属性,因此lookup将在Person.prototype成功。 它会logging下'human'

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

所以现在有趣的部分,所谓的“子类”。 如果我们想要创build一个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) ,这就是我们如何设置StudentStudent.prototype之间的委托关系。 请注意,现在, Student.prototype是空的。 所以查找.species一个实例会失败,因为它委托给Student.prototype ,并且.species属性不存在于Student.prototype

当我们将Student.prototype赋值给Object.create(Person.prototype)Student.prototype本身就委托给Person.prototype ,查找eve.species会像我们预期的那样返回human 。 想必我们希望它从Student.prototype和Person.prototypeinheritance。 所以我们需要解决所有这些。

 /* 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); 

现在代表团工作了,但是我们用Person.prototype覆盖了Student.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属性是一个引用,它引用了我们为了构build一个“构造函数”而编写的函数。 除了new关键字给我们,构造函数是否是一个“普通”的function。

在ES6中, constructor现在被构build到我们编写类的方式中 – 就像在声明一个类时提供的方法一样。 这只是语法糖,但它确实给我们一些便利,比如在扩展现有类时访问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; } } 

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

遵循原始示例,因为您有一个现有对象student1如下所示:

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

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

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

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

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

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

有必要的时候,你需要一个替代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"]); 

编辑,我其实是错的。 评论这条线并不会改变它的行为。 (我testing过)


是的,这是必要的。 当你这样做

 Student.prototype = new Person(); 

Student.prototype.constructor成为Person 。 因此,调用Student()将返回Person创build的对象。 如果你那么做

 Student.prototype.constructor = Student; 

Student.prototype.constructor重置为Student 。 现在当你调用Student()它执行Student ,调用父构造函数Parent() ,它返回正确的inheritance对象。 如果您在调用它之前没有重置Student.prototype.constructor ,那么您将得到一个没有在Student()设置的任何属性的对象。

这些天不需要加糖function“class”或使用“新”。 使用对象文字。

对象原型已经是一个'类'。 当你定义一个对象文字时,它已经是原型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 ++,build立在两个不同实体的概念上:类和实例。

基于原型的语言,如JavaScript,并没有做出这样的区分:它只是有对象。 基于原型的语言具有原型对象的概念,该对象被用作从其获得新对象的初始属性的模板。 任何对象都可以在创build它时或在运行时指定它自己的属性。 另外,任何对象都可以作为另一个对象的原型,从而允许第二个对象共享第一个对象的属性

如果使用工厂而不是原型inheritance的构造函数,则不需要。 其他优点包括:

  • 正确封装私有variables(而不是将它们暴露为this.firstName );
  • 不需要使用new ,而这些东西通常会被忘记作为错误的主要来源;
  • 去除了使用this引用的需要, this引用本身也是已知的混淆源(实际上它负责在链接的例子中设置constructor );
  • 不需要引用object.prototype ,这个方向与自然inheritance相反(从原型到对象);
  • 最后但并非最不重要的一点是,不需要设置Crockford认为是“坏部件”之一的constructor

以下代码演示了如何使用这种方法来避免所有这些问题并获得相同的结果:

 // define factory var CreatePerson = function(firstName) { // revealing pattern return { walk: walk, sayHello: sayHello }; function walk () { console.log("I am walking!"); } // Using the local variable firstName, // which is properly encapsulated and inaccessible from outside function sayHello () { console.log("Hello, I'm " + firstName); } }; // testing the factory - no need for "new"! var girl = CreatePerson("Jane"); girl.walk(); girl.sayHello(); // now define another factory to create objects with additional methods var CreateStudent = function(firstName, subject) { var person = CreatePerson(firstName); var newMethods = { sayHello: function(){ console.log("Hello, I'm " + firstName + ". I'm studying " + subject + "."); }, sayGoodBye: function() { console.log("Goodbye!"); } }; // add new methods var student = Object.assign(person, newMethods); // return the object inheriting from student return Object.create(student); }; // testing the factory var student = CreateStudent("Janet", "Applied Physics"); student.sayHello(); student.sayGoodBye(); 

这里是jsbin玩。

给定简单的构造函数:

 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 ),所有原型自动获得一个名为构造函数的属性,该属性指向函数这是一个财产。 根据构造函数的不同,其他属性和方法可能会被添加到原型中,这是不常见的做法,但仍然允许扩展。

所以简单地回答一下:我们需要确保prototype.constructor中的值正确设置,正如规范所设想的那样。

我们是否必须始终正确设置这个值? 它有助于debugging,并使内部结构与规范保持一致。 我们当然应该在我们的API被第三方使用的时候,而不是在代码最终在运行时执行的时候。

没有必要。 这只是传统的许多事情之一,OOP的冠军们试图将JavaScript的原型inheritance变成经典的inheritance。 下面的唯一的东西

 Student.prototype.constructor = Student; 

确实,你现在有一个当前“构造函数”的参考。

在韦恩的答案中,这被标记为正确的,你可以完全相同的事情,下面的代码

 Person.prototype.copy = function() { // return new Person(this.name); // just as bad return new this.constructor(this.name); }; 

与下面的代码(只需用Personreplacethis.constructor)

 Person.prototype.copy = function() { // return new Person(this.name); // just as bad return new Person(this.name); }; 

感谢上帝,与ES6古典inheritance纯粹主义者可以使用语言的本地运营商,如类,延伸和超级,我们不必看到像prototype.constructor更正和父引用。