Прототипы объектов
Необходимые знания: | Базовая компьютерная грамотность, базовое понимание 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) {
// Определения методов и свойств
this.name = {
first: first,
last: last,
};
this.age = age;
this.gender = gender;
//...см. Введение в объекты для полного определения
}
Затем мы создаём экземпляр объекта следующим образом:
var person1 = new Person("Bob", "Smith", 32, "male", ["music", "skiing"]);
Если вы наберёте «person1.
» в вашей консоли JavaScript, вы должны увидеть, что браузер пытается автоматически заполнить это с именами участников, доступных на этом объекте:
В этом списке вы увидите элементы, определённые в конструкторе person 1 — Person() — name
, age
, gender
, interests
, bio
, и greeting
. Однако вы также увидите некоторые другие элементы — watch
, valueOf
и т. д. — они определены в объекте прототипа Person (), который является Object
.
Итак, что произойдёт, если вы вызываете метод в person1
, который фактически определён в Object
? Например:
person1.valueOf();
Этот метод — Object.valueOf()
наследуется person1
, потому что его конструктором является Person()
, а прототипом Person()
является Object()
. valueOf()
возвращает значение вызываемого объекта — попробуйте и убедитесь! В этом случае происходит следующее:
- Сначала браузер проверяет, имеет ли объект
person1
доступный в нем методvalueOf()
, как определено в его конструктореPerson()
. - Это не так, поэтому следующим шагом браузер проверяет, имеет ли прототип объекта (
Object()
) конструктораPerson()
доступный в нем методvalueOf()
. Так оно и есть, поэтому он вызывается, и все хорошо!
Свойство prototype: Где определены унаследованные экземпляры
Итак, где определены наследуемые свойства и методы? Если вы посмотрите на страницу со ссылкой Object
, вы увидите в левой части большое количество свойств и методов - это намного больше, чем количество унаследованных членов, доступных для объекта person1
. Некоторые из них унаследованы, а некоторые нет - почему это?
Как упоминалось выше, наследованные свойства это те, что определены в свойстве prototype
(вы можете называть это подпространством имён), то есть те, которые начинаются с Object.prototype.
, а не те, которые начинаются с простого Object
. Значение свойства prototype
- это объект, который в основном представляет собой контейнер для хранения свойств и методов, которые мы хотим наследовать объектами, расположенными дальше по цепочке прототипов.
Таким образом Object.prototype.watch()
, Object.prototype.valueOf()
и т. д. доступны для любых типов объектов, которые наследуются от Object.prototype
, включая новые экземпляры объектов, созданные из конструктора Person()
.
Object.is()
, Object.keys()
и другие члены, не определённые в контейнере prototype
, не наследуются экземплярами объектов или типами объектов, которые наследуются от Object.prototype
. Это методы / свойства, доступные только в конструкторе Object()
.
-
Вы можете проверить существующие свойства прототипа для себя - вернитесь к нашему предыдущему примеру и попробуйте ввести следующее в консоль JavaScript:
jsPerson.prototype;
-
Результат покажет вам не много, ведь мы ничего не определили в прототипе нашего конструктора! По умолчанию
prototype
конструктора всегда пуст. Теперь попробуйте следующее:jsObject.prototype;
Вы увидите большое количество методов, определённых для свойства prototype
Object
'а , которые затем доступны для объектов, которые наследуются от Object
, как показано выше.
Вы увидите другие примеры наследования цепочек прототипов по всему JavaScript - попробуйте найти методы и свойства, определённые на прототипе глобальных объектов String
, Date
, Number
и Array
, например. Все они имеют несколько элементов, определённых на их прототипе, поэтому, например, когда вы создаёте строку, вот так:
var myString = "This is my string.";
В myString
сразу есть множество полезных методов, таких как split()
, indexOf()
, replace()
и т. д.
Предупреждение: Важно: Свойство prototype
является одной из наиболее противоречивых названий частей JavaScript - вы можете подумать, что this
указывает на объект прототипа текущего объекта, но это не так (это внутренний объект, к которому можно получить доступ __proto__
, помните ?). prototype
вместо этого - свойство, содержащее объект, на котором вы определяете членов, которые вы хотите наследовать.
Снова create()
Ранее мы показали, как метод Object.create()
может использоваться для создания нового экземпляра объекта.
-
Например, попробуйте это в консоли JavaScript предыдущего примера:
jsvar person2 = Object.create(person1);
-
На самом деле
create()
создаёт новый объект из указанного объекта-прототипа. Здесьperson2
создаётся с помощьюperson1
в качестве объекта-прототипа. Это можно проверить, введя в консоли следующее:jsperson2.__proto__;
Это вернёт объект person1.
Изменение прототипов
Давайте рассмотрим пример изменения свойства prototype
функции-конструктора — методы, добавленные в прототип, затем доступны для всех экземпляров объектов, созданных из конструктора.
-
Вернитесь к нашему примеру oojs-class-further-exercises.html и создайте локальную копию исходного кода. Ниже существующего JavaScript добавьте следующий код, который добавляет новый метод в свойство
prototype
конструктора:jsPerson.prototype.farewell = function () { alert(this.name.first + " has left the building. Bye for now!"); };
-
Сохраните код и загрузите страницу в браузере и попробуйте ввести следующее в текстовый ввод:
jsperson1.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
- его элементы были автоматически обновлены, чтобы включить недавно определённый метод farewell()
.
Вы редко увидите свойства, определённые в свойстве prototype
, потому что они не очень гибки при таком определении. Например, вы можете добавить свойство следующим образом:
Person.prototype.fullName = "Bob Smith";
Это не очень гибко, так как человека нельзя назвать так. Было бы намного лучше сделать это, создав fullName
из name.first
и name.last
:
Person.prototype.fullName = this.name.first + " " + this.name.last;
Однако это не работает, поскольку в этом случае this
будет ссылаться на глобальную область, а не на область функции. Вызов этого свойства вернёт undefined undefined
. Это отлично работало с методом, который мы определили ранее в прототипе, потому что он находится внутри области функций, которая будет успешно перенесена в область экземпляра объекта. Таким образом, вы можете определить постоянные свойства прототипа (т. е. те, которые никогда не нуждаются в изменении), но обычно лучше определять свойства внутри конструктора.
Фактически, довольно распространённый шаблон для большего количества определений объектов - это определение свойств внутри конструктора и методов в прототипе. Это упрощает чтение кода, поскольку конструктор содержит только определения свойств, а методы разделены на отдельные блоки. Например:
// Определение конструктора и его свойств
function Test(a, b, c, d) {
// определение свойств...
}
// Определение первого метода
Test.prototype.x = function() { ... };
// Определение второго метода
Test.prototype.y = function() { ... };
//...и так далее
Этот образец можно увидеть в действии в примере приложения плана школы Петра Залевы.
Резюме
В этой статье рассмотрены прототипы объектов JavaScript (в том числе и то, как прототип цепочки объектов позволяет объектам наследовать функции друг от друга), свойство прототипа и как его можно использовать для добавления методов к конструкторам и другие связанные с этой статьёй темы.
В следующей статье мы рассмотрим то, как вы можете реализовать наследование функциональности между двумя собственными настраиваемыми объектами.