在MDN文章《面向对象Javascript介绍》中关于继承的部分,我注意到他们设置了prototype.constructor:
// correct the constructor pointer because it points to Person
Student.prototype.constructor = Student;
这有什么重要的目的吗?可以省略吗?
在MDN文章《面向对象Javascript介绍》中关于继承的部分,我注意到他们设置了prototype.constructor:
// correct the constructor pointer because it points to Person
Student.prototype.constructor = Student;
这有什么重要的目的吗?可以省略吗?
当前回答
它并不总是必要的,但它确实有它的用途。假设我们想在基类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的实例。这是因为(如果没有显式检查),我们将无法从“基”类返回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
其他回答
给定简单构造函数:
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的属性,该属性指向属性所在的函数。 根据构造函数的不同,可能会将其他属性和方法添加到原型中,这不是很常见的做法,但仍然允许进行扩展。
所以简单地回答:我们需要确保原型中的值。构造函数被正确地设置为规范所假定的样子。
我们必须总是正确地设置这个值吗?它有助于调试,并使内部结构与规范保持一致。当我们的API被第三方使用时,我们肯定应该这样做,但当代码最终在运行时执行时,我们不应该这样做。
TLDR;不是非常必要,但从长远来看可能会有所帮助,这样做更准确。
注意:我之前的回答被编辑了很多,写得令人困惑,有一些错误,我在匆忙回答时漏掉了。感谢那些指出了一些严重错误的人。
基本上,它是在Javascript中正确地连接子类。当我们子类化时,我们必须做一些奇怪的事情来确保原型委托正确工作,包括覆盖原型对象。重写原型对象包括构造函数,因此我们需要修复引用。
让我们快速浏览一下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有一个.species属性,所以查找Person.prototype会成功。然后它将记录'human'。
在这里,Person.prototype.constructor将正确地指向Person。
现在有趣的部分,所谓的“子类化”。如果我们想创建一个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()将返回一个具有我们想要的所有属性的对象。在这里,如果我们检查einstanceof Person,它将返回false。如果我们试图接近夏娃。物种,它将返回undefined。
换句话说,我们需要连接委托,以便eve instanceof Person返回true,以便Student的实例正确地委托给Student。prototype,然后person。prototype。
但是,由于我们使用new关键字调用它,还记得调用添加了什么吗?它会调用object。create(Student。prototype)这就是我们在Student和Student。prototype之间建立委托关系的方式。现在请注意,学生。原型是空的。因此,查找.species Student实例将失败,因为它只委托给Student。在Student.prototype中不存在。species属性。
当我们分配Student时。prototype to Object.create(Person.prototype), Student。原型本身然后委托给Person。原型,和向上看的夏娃。物种会像我们期望的那样回归人类。假设我们希望它从Student继承。原型和人。所以我们需要解决这些问题。
/* 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。Person.prototype的原型。如果我们调用student。prototype。构造函数时,它将指向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中,我们的构造函数属性是一个引用,它指向一个我们写的函数,目的是成为一个“构造函数”。除了new关键字给了我们什么,构造函数在其他方面是一个“普通”函数。
在ES6中,构造函数现在被内置到我们编写类的方式中——也就是说,当我们声明一个类时,它作为一个方法提供。这只是语法上的“糖”,但它确实给我们提供了一些方便,比如在扩展现有类时访问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;
}
}
当你需要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"]);
编辑,其实我错了。注释掉一行并不会改变它的行为。(我测试过)
是的,这是必要的。当你这样做时
Student.prototype = new Person();
student。prototype。constructor变成Person。因此,调用Student()将返回Person创建的对象。如果你这样做了
Student.prototype.constructor = Student;
Student.prototype.constructor被重置回Student。现在,当您调用Student()时,它执行Student,它调用父构造函数parent(),它返回正确继承的对象。如果你在调用Student.prototype.constructor之前没有重置它,你将得到一个没有在Student()中设置的任何属性的对象。
现在不需要加糖的函数'类'或使用'New'。使用对象字面量。
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