Перевод не завершен. Пожалуйста, помогите перевести эту статью с английского.

Прототипы - это механизм по котрому объекты в JavaScript наследуют свойства друг друга и этот механизм отличается от механизма наследования в классических объектно-ориентированных языках. В этой статье мы рассмотрим эти различия, разберем как работают цепочки прототипов и посмотрим как свойство prototype может использоваться для добавления методов к существующим конструкторам.

Необходимые условия:

Базовая компьютерная грамотность, базовое понимание HTML и CSS, знакомство с основами JavaScript (см. Первые шаги и Строительные блоки) и основы OOJS (см. Введение в объекты).

Задача:

Понять прототипы объектов JavaScript, как работают прототипные цепочки и как добавить новые методы в prototype свойство.

Язык основанный на прототипах?

JavaScript часто описывают как язык прототипного-наследования — каждый объект, имеет объект-прототип, который выступает как шаблон от которого объект наследует методы и свойства. Объект-прототип так же может иметь свой прототип и наследовать его свойства и методы и так далее. Это часто называется цепочкой прототипов и объясняет почему одним объектам доступны свойства и методы которые определены в других объектах.

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

В классическом ООП определяются классы, затем, когда экземпляры объектов создаются, все свойства и методы, определенные в классе, копируются в экземпляр. В JavaScript они не копируются - вместо этого создается ссылка из экземпляра объекта на его прототип (это свойство __proto__, которое получено из свойства prototype в конструкторе), а свойства и методы обнаруживаются путем подъема по цепи прототипов.

Примечание: Важно понимать, что существует различие между прототипом объекта (который доступен через Object.getPrototypeOf(obj) или через устаревшее свойство __proto__) и свойство prototype в конструкторских функциях. Первый - это свойство для каждого экземпляра, а последнее - свойство на конструкторе. То есть Object.getPrototypeOf(new Foobar()) относится к тому же объекту, что и Foobar.prototype.

Давайте посмотрим на пример, чтобы сделать это немного яснее.

Понимание прототипа объектов

Вернемся к примеру, когда мы закончили писать наш конструктор Person() - загрузите пример в свой браузер. Если у вас еще нет работы от последней статьи, используйте наш пример oojs-class-further-exercises.html (см. Также исходный код).

В этом примере мы определили конструкторную функцию, например:

function Person(first, last, age, gender, interests) {
  
  // определение свойств и методов
  
}

Затем мы создаём экземпляр объекта следующим образом:

var person1 = new Person('Bob', 'Smith', 32, 'male', ['music', 'skiing']);

Если вы наберете «person1». в вашей консоли JavaScript, вы должны увидеть, что браузер пытается автоматически заполнить это с именами участников, доступных на этом объекте:

В этом списке вы увидите участников, определенных на объекте прототипа person1, который является Person() (Person () является конструктором) - name, age, gender, interests, bio и greeting. Однако вы также увидите некоторые другие члены - watch, valueOf и т. д. - они определены на объекте прототипа Person(), который является Object. Это демонстрирует работу прототипа.

Итак, что произойдет, если вы вызываете метод на person1, который фактически определен в Object? Например:

person1.valueOf()

Этот метод просто возвращает значение объекта, на который он вызван, - попробуйте его и посмотрите! В этом случае происходит следующее:

  • Сначала браузер проверяет, имеет ли объект person1 метод valueOf(), доступный на нем.
  • Это не так, поэтому браузер затем проверяет, есть ли прототип объекта person1 (Person() конструктор портотипов) имеющегося метода valueOf().
  • It doesn't either, so the browser then checks to see if the Person() constructor's prototype object's prototype object (Object() constructor's prototype) has a valueOf() method available on it. It does, so it is called, and all is good!

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

Примечание: Официально нет способа прямого доступа к объекту прототипа объекта - «ссылки» между элементами в цепочке определены во внутреннем свойстве, называемом [[prototype]] в спецификации для языка JavaScript ( см. ECMAScript). Однако у большинства современных браузеров есть свойство, доступное для них под названием __proto__ (это 2 подчеркивания с обеих сторон), который содержит объект-прототип объекта-конструктора. Например, попробуйте person1.__proto__ и person1.__proto__.__proto__, чтобы увидеть, как выглядит цепочка в коде!

С ECMAScript 2015 вы можете косвенно обращаться к объекту прототипа объекта Object.getPrototypeOf (obj).

Свойство prototype: если унаследованные члены определены

Итак, где определены наследуемые свойства и методы? Если вы посмотрите на страницу ссылки Object, вы увидите в левой части большое количество свойств и методов - это намного больше, чем количество унаследованных членов, которые мы видели на объекте person1 в приведенном выше скриншоте. Некоторые из них унаследованы, а некоторые нет - почему это?

Ответ заключается в том, что унаследованные - это те, которые определены в свойстве prototype (вы можете назвать это подпространством имен), то есть те, которые начинаются с Object.prototype., а не те, которые начинаются с простого Object. Значение свойства prototype - это объект, который в основном представляет собой ведро для хранения свойств и методов, которые мы хотим наследовать объектами, расположенными дальше по цепочке прототипов.

Таким образом Object.prototype.watch(), Object.prototype.valueOf() и т.д. Доступны для любых типов объектов, которые наследуются от Object.prototype, включая новые экземпляры объектов, созданные из конструктора.

Object.is(), Object.keys() и другие члены, не определенные внутри ведра prototype, не наследуются экземплярами объектов или типами объектов, которые наследуются от Object.prototype. Это методы / свойства, доступные только для самого конструктора Object().

Примечание: Это кажется странным - как у вас есть метод, определенный для конструктора, который сам по себе является функцией? Ну, функция также является типом объекта - см. Ссылку на конструктор Function(), если вы нам не верите.

  1. Вы можете проверить существующие свойства прототипа для себя - вернитесь к нашему предыдущему примеру и попробуйте ввести следующее в консоль JavaScript:
    Person.prototype
  2. Результат не покажет вам очень много - ведь мы ничего не определили в прототипе нашего конструктора! По умолчанию prototype конструктора всегда пуст. Теперь попробуйте следующее:
    Object.prototype

Вы увидите большое количество методов, определенных для свойства Object's prototype, которые затем доступны для объектов, которые наследуются от Object, как показано выше.

Вы увидите другие примеры наследования цепочек прототипов по всему JavaScript - попробуйте найти методы и свойства, определенные на прототипе глобальных объектов String, Date, Number и Array, например. Все они имеют несколько элементов, определенных на их прототипе, поэтому, например, когда вы создаете строку, вот так:

var myString = 'This is my string.';

В myString сразу есть множество полезных методов, таких как split(), indexOf(), replace() и т. д.

Важно: Свойство prototype является одной из наиболее противоречивых названий частей JavaScript - вы можете подумать, что this указывает на объект прототипа текущего объекта, но это не так (это внутренний объект, к которому можно получить доступ __proto__, помните ?). prototype вместо этого - свойство, содержащее объект, на котором вы определяете членов, которые вы хотите наследовать.

Повторное создание create()

Ранее мы показали, как метод Object.create() может использоваться для создания нового экземпляра объекта.

  1. Например, попробуйте это в консоли JavaScript предыдущего примера:
    var person2 = Object.create(person1);
  2. Фактически создание create() - это создание нового объекта из указанного объекта прототипа. Здесь person2 создается с использованием person1 в качестве объекта-прототипа. Вы можете проверить это, введя в консоли следующее:
    person2.__proto__

Это вернет объект Person.

Свойство конструктора

Каждая функция-конструктор имеет свойство prototype, значение которого является объектом, содержащим свойство constructor. Это свойство конструктора указывает на исходную конструкторскую функцию. Как вы увидите в следующем разделе, свойства, определенные в свойстве Person.prototype (или в общем случае в качестве свойства прототипа функции конструктора, который является объектом, как указано в предыдущем разделе) становятся доступными для всех объектов экземпляра, созданных с помощью конструктор Person(). Следовательно, свойство конструктора также доступно для объектов person1 и person2.

  1. Например, попробуйте эти команды в консоли:
    person1.constructor
    person2.constructor

    Они должны возвращать конструктор Person(), поскольку он содержит исходное определение этих экземпляров.

    Умный трюк заключается в том, что вы можете поместить круглые скобки в конец свойства constructor (содержащие любые требуемые параметры) для создания другого экземпляра объекта из этого конструктора. Конструктор - это функция в конце концов, поэтому ее можно вызвать с помощью круглых скобок; вам просто нужно включить new ключевое слово, чтобы указать, что вы хотите использовать эту функцию в качестве конструктора.

  2. Попробуйте это в консоли:
    var person3 = new person1.constructor('Karen', 'Stephenson', 26, 'female', ['playing drums', 'mountain climbing']);
  3. Теперь попробуйте получить доступ к функциям вашего нового объекта, например:
    person3.name.first
    person3.age
    person3.bio()

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

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

instanceName.constructor.name

Попробуйте это, например:

person1.constructor.name

Примечание: Значение constructor.name может измениться (из-за прототипического наследования, привязки, препроцессоров, транспилеров и т. Д.), Поэтому для более сложных примеров вы захотите использовать оператор instanceof.

Изменение прототипов

Давайте рассмотрим пример изменения свойства prototype функции-конструктора (методы, добавленные в прототип, затем доступны для всех экземпляров объектов, созданных из конструктора).

  1. Вернитесь к нашему примеру oojs-class-further-exercises.html и создайте локальную копию исходного кода. Ниже существующего JavaScript добавьте следующий код, который добавляет новый метод в свойство prototype конструктора:
    Person.prototype.farewell = function() {
      alert(this.name.first + ' has left the building. Bye for now!');
    };
  2. Сохраните код и загрузите страницу в браузере и попробуйте ввести следующее в текстовый ввод:
    person1.farewell();

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

Подумайте об этом на мгновение. В нашем коде мы определяем конструктор, затем мы создаем экземпляр объекта из конструктора, затем добавляем новый метод к прототипу конструктора:

function Person(first, last, age, gender, interests) {

  // определения свойств и методов

}

var person1 = new Person('Tammi', 'Smith', 32, 'neutral', ['music', 'skiing', 'kickboxing']);

Person.prototype.farewell = function() {
  alert(this.name.first + ' has left the building. Bye for now!');
};

Но метод farewell() по-прежнему доступен на экземпляре объекта person1 - его доступные функции были автоматически обновлены.

Note: If you are having trouble getting this example to work, have a look at our oojs-class-prototype.html example (see it running live also).

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

Person.prototype.fullName = 'Bob Smith';

Это не очень гибко, так как человека нельзя назвать так. Было бы намного лучше сделать это, чтобы построить fullName из name.first и name.last:

Person.prototype.fullName = this.name.first + ' ' + this.name.last;

Однако это не работает, поскольку в этом случае this будет ссылка на глобальную область, а не на область действия. Вызов этого свойства вернет undefined undefined. Это отлично работало над методом, который мы определили ранее в прототипе, потому что он находится внутри области функций, которая будет успешно перенесена в область экземпляра объекта. Таким образом, вы можете определить постоянные свойства прототипа (т. е. те, которые никогда не нуждаются в изменении), но обычно лучше определять свойства внутри конструктора.

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

// Constructor with property definitions

function Test(a, b, c, d) {
  // property definitions
}

// First method definition

Test.prototype.x = function() { ... };

// Second method definition

Test.prototype.y = function() { ... };

// etc.

Этот образец можно увидеть в действии в примере приложения плана школы Петра Залевы.

Резюме

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

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

 

In this module

 

Метки документа и участники

Внесли вклад в эту страницу: VaselisaS, slychai85, arnoldovich, AndreySushentsov
Обновлялась последний раз: VaselisaS,