Почему необходимо установить конструктор прототипа?



на раздел о наследовании в статье MDN введение в объектно-ориентированный Javascript, я заметил, что они создают прототип.конструктор:



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


служит ли это какой-либо важной цели? Это нормально, чтобы опустить его?

524   12  

12 ответов:

это не всегда необходимо, но у него есть свои применения. Предположим, мы хотели сделать метод копирования на базе 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 свойство будет иметь его и что он будет ссылаться на функцию, и что это. Ничто другое в спецификации не относилось к нему вообще.

что изменилось в ES2015 (ES6), который начал использовать его в отношении в иерархии наследования. Например, Promise#then использует constructor свойство обещания, которое вы вызываете (через SpeciesConstructor) при строительстве нового обещают вернуться. Он также участвует в массивах подтипов (через ArraySpeciesCreate).

за пределами самого языка, иногда люди будут использовать его при попытке построить общие функции "клон" или просто обычно, когда они хотят ссылаться на то, что они считали бы функции конструктора объекта. Мой опыт заключается в том, что использование его редко, но иногда люди используют его.

это нормально, чтобы пропустить его?

это там по умолчанию, вам нужно только положить его обратно, когда вы заменить объект на функции prototype свойства:

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

если вы этого не сделаете:

Student.prototype.constructor = Student;

...тогда Student.prototype.constructor наследует от Person.prototype, который (предположительно) был constructor = Person. Так что это вводит в заблуждение. И конечно, если вы подкласс что-то, что использует его (например Promise или Array) и не используя class1 (который обрабатывает это для вас), вы хотите, чтобы убедиться, что вы установите его правильно. Так что в принципе: это хорошая идея.

это нормально, если ничто в вашем коде (или коде библиотеки, который вы используете) не использует его. Я всегда следил за тем, чтобы он был правильно подключен.

конечно, с ES2015 (aka ES6)'s class ключевое слово, большую часть времени мы бы использовали его, нам больше не нужно, потому что это обрабатывается для нас, когда мы делаем

class Student extends Person {
}

1 "...если вы подкласс что-то, что использует его (например Promise или Array) и не используя class..." - это возможно чтобы сделать это, но это реальная боль (и немного глупо). Вы должны использовать Reflect.construct.

Я бы не согласился. Не нужно устанавливать прототип. Возьмите тот же самый код, но удалите прототип.строка конструктора. Что-нибудь изменилось? Нет. Теперь внесите следующие изменения:

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

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

и в конце тестового кода...

alert(student1.favoriteColor);

цвет будет синий.

изменение прототипа.конструктор, по моему опыту, не делает много, если вы не делаете очень конкретные, очень сложные вещи, которые, вероятно, не являются хорошей практикой в любом случае :)

изменить: После того, как вы немного покопались в интернете и провели некоторые эксперименты, похоже, что люди установили конструктор так, что он "выглядит" как вещь, которая строится с помощью "нового". Я думаю, я бы сказал, что проблема с этим заключается в том, что javascript является языком - прототипом-нет такой вещи, как наследование. Но большинство программистов происходят из фона программирования, который толкает наследование как "путь". Поэтому мы придумываем всевозможные вещи, чтобы попытаться сделать это прототипический язык "классический" язык.. например, расширение "классов". Действительно, в приведенном примере новый студент - это человек, он не "расширяется" от другого студента.. ученик-это все о человеке, и кем бы он ни был, ученик тоже есть. Расширьте студента, и все, что вы расширили, является студентом в глубине души, но настроено в соответствии с вашими потребностями.

Крокфорд немного сумасшедший и чрезмерно усердный, но сделайте серьезное чтение о некоторых вещах, которые он написанный.. это заставит вас взглянуть на этот материал совсем по-другому.

TLDR; не супер необходимо, но, вероятно, поможет в долгосрочной перспективе, и это более точно, чтобы сделать это.

Примечание: многое отредактировано, поскольку мой предыдущий ответ был путано написан и имел некоторые ошибки, которые я пропустил в своем стремлении ответить. Спасибо тем, кто указал на некоторые вопиющие ошибки.

в основном, это правильно подключать подклассы в Javascript. Когда мы подкласс, мы должны сделать некоторые фанки вещи, чтобы убедиться, что прототипное делегирование работает правильно, включая перезапись a

Это имеет огромную ловушку, что если вы написали

Student.prototype.constructor = Student;

но тогда, если бы был учитель, прототипом которого был также Человек, и вы написали

Teacher.prototype.constructor = Teacher;

тогда студент конструктор теперь учитель!

изменить: Этого можно избежать, установив прототипы учащихся и учителей с использованием новых экземпляров класса Person, созданных с помощью объекта.создайте, как в примере Mozilla.

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

до сих пор путаница все еще есть.

следуя исходному примеру, как у вас есть существующий объект student1 как:

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

Предположим, вы не хотите знать, как student1 создается, вы просто хотите другой объект, как это, вы можете использовать свойство конструктора student1 как:

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

здесь он не сможет получить свойства от Student если свойство конструктора не задано. Скорее это создаст

получил хороший пример кода, почему действительно необходимо установить конструктор прототипа..

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

нет необходимости в подслащенных функциональных "классах" или использовании "новых" в эти дни. Используйте объектные литералы.

прототип объекта уже является "классом". При определении литерала объекта он уже является экземпляром объекта-прототипа. Они также могут выступать в качестве прототипа другого объекта и т. д.

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

EDIT, я был на самом деле неправ. Комментирование линии не меняет ее поведения вообще. (Я проверил его)


Да, это необходимо. Когда вы делаете

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), все прототипы автоматически получают свойство, называемое конструктором, которое указывает на функцию, для которой оно является свойством. В зависимости от конструктора, другие свойства и методы могут быть добавлены к прототипу, который не является очень распространенной практикой, но все же это так допускается расширение.

поэтому просто отвечаю: нам нужно убедиться, что значение в прототипе.конструктор правильно установлен, как это предполагается спецификацией.

нужно ли всегда правильно устанавливать это значение? Это помогает при отладке и делает внутреннюю структуру в соответствии со спецификацией. Мы должны определенно, когда наш API используется третьими сторонами, но не совсем, когда код, наконец, выполняется во время выполнения.

не надо. Это всего лишь одна из многих традиционных вещей, которые делают чемпионы ООП, чтобы попытаться превратить прототипическое наследование JavaScript в классическое наследование. Единственное, что после

Student.prototype.constructor = Student; 

делает, что теперь у вас есть ссылка нынешней "конструктор".

в ответе Уэйна, который был отмечен как правильный, вы можете сделать то же самое, что и следующий код

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

С кодом ниже (просто заменить этот.конструктор с человеком)

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

слава Богу, что с классическим наследованием ES6 пуристы могут использовать родные операторы языка, такие как class, extends и super, и нам не нужно видеть прототип.исправления конструктора и родительские ссылки.

Comments

    Ничего не найдено.