Why is it necessary to set the prototype constructor?
在MDN文章介绍面向对象的javascript中关于继承的部分中,我注意到他们设置了prototype.constructor:
1 2 | // correct the constructor pointer because it points to Person Student.prototype.constructor = Student; |
这有什么重要的用途吗?可以忽略它吗?
它并不总是必要的,但它确实有它的用途。假设我们想在基础
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | // 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); |
现在,当我们创建一个新的
1 2 | var student1 = new Student("trinth"); console.log(student1.copy() instanceof Student); // => false |
副本不是
1 2 | // correct the constructor pointer because it points to Person Student.prototype.constructor = Student; |
…然后一切按预期工作:
1 2 | var student1 = new Student("trinth"); console.log(student1.copy() instanceof Student); // => true |
Does this serve any important purpose?
是和不是。
在ES5和更早的版本中,javascript本身没有为任何东西使用
这在ES2015(ES6)中发生了变化,它开始在继承层次结构中使用它。例如,
在语言本身之外,有时人们会在尝试构建通用的"克隆"函数时使用它,或者只是在一般情况下,当他们想要引用他们认为是对象的构造函数函数时使用它。我的经验是很少使用它,但有时人们确实使用它。
Is it okay to omit it?
默认情况下,只需在替换函数的
1 | Student.prototype = Object.create(Person.prototype); |
如果不这样做:
1 | Student.prototype.constructor = Student; |
…然后,
如果您的代码(或您使用的库代码)中没有任何东西使用它,也可以。我一直确保它的接线正确。
当然,有了ES2015(又称ES6)的
1 2 | class Student extends Person { } |
1"…如果你将使用它的东西(如
TLDR;不是非常必要,但从长远来看可能会有所帮助,而且这样做更准确。
注:由于我以前的答案被编辑得很混乱,在我急于回答的过程中有一些错误我错过了。感谢那些指出了一些令人震惊的错误的人。
基本上,它是在JavaScript中正确地连接子类。当我们进行子类化时,我们必须做一些奇怪的事情来确保原型委托的工作正常,包括覆盖
让我们快速了解ES5中的"类"是如何工作的。
假设您有一个构造函数函数及其原型:
1 2 3 4 5 6 7 8 9 10 | //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', } |
调用构造函数进行实例化时,如
1 2 | // instantiate using the 'new' keyword var adam = new Person('Adam', 19); |
使用"person"调用的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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 * } */ |
如果我们
在这里,
现在有趣的部分,所谓的"子类化"。如果我们想创建一个
它本身并不能做到这一点。子类化时,代码如下所示:
1 2 3 4 5 6 7 8 9 10 | 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: {} |
在这里调用
换言之,我们需要召集代表团,使
但既然我们用
当我们把
1 2 3 4 5 6 7 8 9 | /* 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); |
现在代表团工作了,但我们用一个
1 2 3 4 5 | // 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中,我们的
在ES6中,EDOCX1[2]现在构建在我们编写类的方式中——就像在中一样,它在我们声明类时作为一种方法提供。这只是简单的句法糖分,但它确实给了我们一些便利,比如在扩展现有类时访问
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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; } } |
我不同意。不需要设置原型。采用完全相同的代码,但删除prototype.constructor行。有什么变化吗?否。现在,进行以下更改:
1 2 3 4 5 6 7 8 | Person = function () { this.favoriteColor = 'black'; } Student = function () { Person.call(this); this.favoriteColor = 'blue'; } |
在测试代码的末尾…
1 | alert(student1.favoriteColor); |
颜色将是蓝色。
根据我的经验,对prototype.constructor的更改并没有多大作用,除非您正在做一些非常具体、非常复杂的事情,这些事情可能无论如何都不是很好的实践:)
编辑:在浏览了一下Web并进行了一些实验之后,人们似乎设置了构造函数,使其"看起来"像是用"new"构建的东西。我想我会争辩说,这方面的问题是,javascript是一种原型语言——没有继承这样的东西。但是大多数程序员都来自于将继承性作为"方式"来推动的编程背景。所以我们想出了各种各样的办法,试图使这种原型语言成为一种"经典"语言。例如扩展"类"。实际上,在他们给出的例子中,一个新学生就是一个人——它不是从另一个学生那里"延伸"出来的。学生是关于人的,不管人是什么,学生也是。扩展学生,不管你扩展了什么,都是一个学生,但它是根据你的需要定制的。
克罗克福德有点疯狂和过分热情,但认真阅读他写的一些东西。这会让你对这些东西的看法大相径庭。
这有一个巨大的陷阱,如果你写
1 | Student.prototype.constructor = Student; |
但如果有一个老师的原型也是人,你写的
1 | Teacher.prototype.constructor = Teacher; |
那么学生建造师现在是老师了!
编辑:您可以通过确保使用使用object.create创建的Person类的新实例(如Mozilla示例中所示)设置学生和教师原型来避免这种情况。
1 2 | Student.prototype = Object.create(Person.prototype); Teacher.prototype = Object.create(Person.prototype); |
到目前为止,混乱仍然存在。
按照原始示例,由于现有对象
1 | var student1 = new Student("Janet","Applied Physics"); |
假设您不想知道
1 | var student2 = new student1.constructor("Mark","Object-Oriented JavaScript"); |
如果没有设置constructor属性,它将无法从
得到了一个很好的代码示例,说明为什么需要设置原型构造函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | 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 ???? */ |
它是必要的,当你需要一个新的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | //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"]); |
现在不需要加糖函数"classes"或使用"new"。使用对象文本。
对象原型已经是一个"类"。定义对象文字时,它已经是原型对象的实例。它们也可以作为另一个对象的原型等。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | 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
这里是一个例子,我发现非常有用的MDN明白其使用。
在JavaScript中,我们
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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 }); |
非常简单的构造函数的函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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(){...} |
默认(从规范developer.mozilla.org http:///EN /文档/网络/美国/全球_ JavaScript参考/对象/对象/构造函数),都称为构造函数的原型自动得到一点回,物业的功能上,它是一个物业。取决于其他的构造函数,属性和方法可以被添加到的原型是一个普通的实践困境,但仍然是允许的扩展。
所以简单的回答:我们需要确保的是,在一个正确的价值prototype.constructor集,它是由不规范的。
我们总是做正确设置这个值?它可以帮助在调试和内部结构与规范一致。我们应该肯定的,当我们使用的API是由thirdparties,但不是真的当是最后执行的代码在运行时。
编辑,我错了。评论这一行根本不会改变它的行为。(我测试过)
是的,这是必要的。当你这样做的时候
1 | Student.prototype = new Person(); |
1 | Student.prototype.constructor = Student; |
这是不必要的。它是只是一个传统的OOP做许多的事情,尝试体操冠军prototypical JavaScript的继承和继承的。唯一的东西,
1 | Student.prototype.constructor = Student; |
是,是,你现在有一个参考的电流"建设者"。
在韦恩已经标记为答案,这是正确的,你可以在同一确切的东西下面的代码是
1 2 3 4 | Person.prototype.copy = function() { // return new Person(this.name); // just as bad return new this.constructor(this.name); }; |
下面的代码是一个传统的人this.constructor)
1 2 3 4 | Person.prototype.copy = function() { // return new Person(this.name); // just as bad return new Person(this.name); }; |
感谢上帝,在继承古典es6 purists CAN使用语言的本地运营商扩展类和超类,我们不prototype.constructor湖样校正和家长参考。