Почему необходимо установить конструктор прототипа?
на раздел о наследовании в статье MDN введение в объектно-ориентированный Javascript, я заметил, что они создают прототип.конструктор:
// correct the constructor pointer because it points to Person
Student.prototype.constructor = Student;
служит ли это какой-либо важной цели? Это нормально, чтобы опустить его?
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