Почему необходимо установить конструктор прототипа? - PullRequest
276 голосов
/ 10 декабря 2011

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

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

Служит ли это какой-либо важной цели? Можно ли это опустить?

Ответы [ 13 ]

249 голосов
/ 10 декабря 2011

Это не всегда необходимо, но у него есть свои применения.Предположим, мы хотим создать метод копирования для базового класса 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
70 голосов
/ 21 февраля 2016

Служит ли это какой-либо важной цели?

Да и нет.

В 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) и не используете class ¹ (который обрабатывает это для вас), вы захотите убедиться, что вы установили его правильно. Итак, в основном: это хорошая идея.

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

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

class Student extends Person {
}

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

11 голосов
/ 27 февраля 2016

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, котораяis Person.prototype - и Person.prototype имеет свойство a .species, поэтому поиск будет успешным на Person.prototype.Затем он будет регистрировать 'human'.

Здесь Person.prototype.constructor будет правильно указывать на Person.

Так что теперь интересная часть, так называемое «подклассирование».Если мы хотим создать класс Student, который является подклассом класса Person, с некоторыми дополнительными изменениями, нам нужно убедиться, что Student.prototype.constructor указывает на точность ученика.

Это не делает это само по себе.Когда вы создаете подкласс, код выглядит следующим образом:

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 завершится неудачей, поскольку он делегирует only Student.prototype, а свойство .species не существует в Student.prototype.

Когда мы присваиваем Student.prototype Object.create(Person.prototype), Student.prototype, то делегируем Person.prototype, и поиск eve.species возвращает human, как мы и ожидаем.Предположительно, мы бы хотели, чтобы он наследовал от Student.prototype AND 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, когда мы расширяем существующий класс.Поэтому мы бы написали приведенный выше код так:

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;
   }
}
10 голосов
/ 10 декабря 2011

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

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

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

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

alert(student1.favoriteColor);

Цвет будет синим.

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

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

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

9 голосов
/ 27 августа 2014

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

Student.prototype.constructor = Student;

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

Teacher.prototype.constructor = Teacher;

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

Edit: Вы можете избежать этого, убедившись, что вы установили прототипы Student и Teacher, используя новые экземпляры класса Person, созданные с помощью Object.create, как в примере Mozilla.

Student.prototype = Object.create(Person.prototype);
Teacher.prototype = Object.create(Person.prototype);
5 голосов
/ 30 сентября 2015

Пока что все еще существует путаница.

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

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

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

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

Здесь не удастся получить свойства из Student, если конструкторсвойство не установлено.Скорее он создаст объект Person.

2 голосов
/ 25 июля 2014

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

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 голос
/ 29 сентября 2016

Это необходимо, когда вам нужна альтернатива toString без обезьяноподготовки:

//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"]);
1 голос
/ 31 мая 2016

В наши дни не нужно использовать классы с сахаром или использовать «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]

Это стоит прочитать :

Объектно-ориентированные языки на основе классов, такие как Java и C ++, основаны на концепции двух отдельных объектов: классов и экземпляры.

...

Язык, основанный на прототипах, такой как JavaScript, не делает этого Различие: у него просто есть объекты. Язык на основе прототипа имеет Понятие прототипа объекта, объекта, используемого в качестве шаблона из который получить начальные свойства для нового объекта. Любой предмет может указать его собственные свойства, либо при его создании, либо во время выполнения. Кроме того, любой объект может быть связан как прототип для другого объект, позволяющий второму объекту совместно использовать первый объект Свойства

0 голосов
/ 14 января 2019

Вот один пример из 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
});
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...