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

2020/10/05 02:41 · javascript ·  · 0评论

MDN文章“面向对象的Java语言简介”中有关继承部分中,我注意到它们设置了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

这有任何重要目的吗?

是的,没有。

在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).

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 replace the object on a function's prototype property:

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

如果您不这样做:

Student.prototype.constructor = Student;

...然后Student.prototype.constructor继承Person.prototype(大概是)从中继承constructor = Person因此,这具有误导性。当然,如果要对使用它的东西(例如PromiseArray)进行子类化,而不使用class¹(可以为您处理),则需要确保正确设置。基本上,这是个好主意。

如果您的代码(或您使用的库代码)中没有任何内容,则可以使用。我一直确保它已正确连接。

当然,有了ES2015(aka ES6)的class关键字,大多数时候我们会使用它,我们不再需要了,因为当我们这样做时,它会为我们处理

class Student extends Person {
}

¹ “ ...如果将使用它的东西(例如PromiseArray子类化而不使用class...”  –可以这样做,但这确实很痛苦(有点傻)。您必须使用Reflect.construct

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

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

如果我们console.log(adam.species),查找失败的adam实例,并期待在原型链的.prototype,这是Person.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()此处将返回具有我们想要的所有属性的对象。在这里,如果我们检查eve instanceof Person,它将返回false如果我们尝试访问eve.species,它将返回undefined

换句话说,我们需要连接委托,以便eve instanceof Person返回true,以便将Student委托的实例正确地传递给Student.prototype,然后Person.prototype

但是由于我们使用new关键字来调用它,还记得该调用添加了什么吗?它会调用Object.create(Student.prototype),这就是我们在Student之间建立这种委托关系的方式Student.prototype请注意,现在Student.prototype为空。因此查找.species的实例Student将失败,因为它 委托给Student.prototype,并且该.species属性在上不存在Student.prototype

当我们确实将分配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);

现在,该委派工作了,但是我们正在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

在ES5中,我们的constructor属性是一个引用,引用了我们为成为“构造函数”而编写的函数。除了new关键字提供给我们的功能以外,构造函数还具有“普通”功能。

在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;
   }
}

我不同意。无需设置原型。采取完全相同的代码,但删除prototype.constructor行。有什么变化吗?否。现在,进行以下更改:

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

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

在测试代​​码的末尾...

alert(student1.favoriteColor);

颜色为蓝色。

根据我的经验,对prototype.constructor的更改不会做很多事情,除非您要进行非常具体,非常复杂的操作,但这些操作可能都不是一个好习惯:)

编辑:在网络上闲逛了一段时间并做了一些试验之后,看起来人们在设置构造函数,以便它“看起来”像是正在用“ new”构造的东西。我想我会认为这是Javascript是一种原型语言的问题-没有继承性。但是大多数程序员都来自将继承视为“方式”的编程背景。因此,我们想出了各种方法来尝试使这种原型语言成为一种“经典”语言,例如扩展“类”。确实,在他们给出的示例中,一个新学生是一个人-它不是从另一个学生“扩展”过来的。该学生就是这个人的全部,而无论这个人是哪个学生,也是如此。扩展学生,无论您是什么

克罗克福德(Crockford)有点疯狂和狂热,但是请认真阅读他所写的一些东西。这会让您对这些东西的看法大为不同。

这有一个巨大的陷阱,如果你写

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

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

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

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

假设您不想知道如何student1创建,只想要一个类似的对象,则可以使用student1like的构造器属性

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

Student如果未设置构造函数属性,将无法从中获取属性而是将创建一个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 ???? 
*/

如今,不需要加糖的函数“类”或使用“新建”。使用对象文字。

对象原型已经是一个“类”。定义对象文字时,它已经是原型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)没有这种区别:它只是具有对象。基于原型的语言具有原型对象的概念,该对象用作模板,从模板获取新对象的初始属性。任何对象都可以在创建对象时或在运行时指定其自己的属性。此外,任何对象都可以关联为另一个对象的原型,从而允许第二个对象共享第一个对象的属性

当您需要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()

给定简单的构造函数:

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中的值正确设置为规范所假定的值。

我们是否必须始终正确设置此值?它有助于调试,并使内部结构与规范保持一致。我们肯定应该在第三方使用我们的API时使用,而不是在运行时最终执行代码时才使用。

这是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
});

有必要。类继承中的任何类都必须具有自己的构造函数,就像原型继承中那样,这也便于对象构造。但是问题不是必需的,而必须了解在JavaScript世界中调用函数作为构造函数的作用以及解决对象属性的规则。

用表达式new <函数名>([参数])将函数作为构造函数执行的效果

  1. 创建一个类型名称为函数名称的对象
  2. 函数中的内部属性附加到创建的对象
  3. 函数的属性原型将作为原型自动附加到创建的对象

对象解析属性的规则

  • 不仅要在对象上搜索属性,还要在对象的原型,原型的原型上等等,直到找到具有匹配名称的属性或到达原型链的末尾为止。

基于这些基本机制,语句<constructor name> .prototype.constructor = <constructor name>在效果上等于将构造函数附加到表达式this.constructor = <constructor name>的构造器主体中如果是第二种话语,则构造函数将在对象上解析;如果是第二种话语,则将在对象的原型上解析。

这不是必要的。OOP拥护者只是尝试将JavaScript的原型继承转换为经典继承而做的许多传统工作之一。以下唯一的一点

Student.prototype.constructor = Student; 

确实是,您现在有了当前“构造函数”的引用。

在韦恩的答案中,这已被标记为正确,您可以与以下代码完全一样

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,经典继承者就可以使用语言的本机运算符,例如class,extends和super,而我们不必像prototype.constructor更正和父代引用一样。

本文地址:http://javascript.askforanswer.com/weishenmexuyaoshezhiyuanxinggouzaohanshu.html
文章标签: ,   ,  
版权声明:本文为原创文章,版权归 javascript 所有,欢迎分享本文,转载请保留出处!

文件下载

老薛主机终身7折优惠码boke112

上一篇:
下一篇:

评论已关闭!