Введение в прототипы JavaScript
Объектно-ориентированный JavaScript
Хотя JavaScript не является языком, основанным на классах, и не реализует объектно-ориентированное программирование в традиционном смысле, он предоставляет возможности и шаблоны, которые позволяют использовать концепции объектно-ориентированного программирования. Это можно назвать прототипным наследованием.
Что такое прототип в JavaScript?
Основная концепция прототипа в JavaScript заключается в том, что один объект может наследовать свойства и методы от другого. На приведенном ниже генеалогическом дереве видно, что люди в нижней части связаны с одним человеком на вершине, и путь снизу вверх можно проследить шаг за шагом.
В JavaScript прототип можно рассматривать как проект, шаблон или базовый объект, содержащий методы, к которым может обратиться любой созданный экземпляр объекта. Если бы не было прототипа, то пришлось бы определять методы для каждого отдельного экземпляра объекта. Этот процесс неэффективен и занимает много памяти.
Разберем два прототипа: свойство prototype
и сам прототип объекта. Начнем со свойства prototype
.
Свойство prototype
Когда мы используем методы JavaScript, такие как pop
в отношении массива или include
при работе со строками, мы применяем встроенные методы JavaScript. Рассмотрим это на примере. Начнем с простого доступа к свойству prototype
в конструкторе прототипов Array
.
Теперь создадим массив и посмотрим, как можно просматривать свойство prototype
внутри массива.
В приведенном выше примере переменная ourArray
объявляется и инициализируется массивом, содержащим значения 1, 2 и 3. Позже нужно будет рассмотреть массив ourArray
. Чтобы удалить элемент из конца массива, можно использовать метод pop
. Но откуда он берется? Взглянем на массив ourArray
еще раз.
Каждый объект в JavaScript имеет свойство . Это внутреннее свойство, которое обозначается двойными скобками, в которые оно заключено. Выбрав свойство
, мы видим доступные методы, которые в данном примере наследуются от конструктора
Array
. Мы также можем использовать метод getPrototypeOf
, который вернет свойство данного объекта (помните, что массивы ?—? это особый тип объектов).
Вы также можете встретить примеры, в которых используется свойство __proto__
. Это унаследованная функция, и в eslint есть правило (no-proto), защищающее от ее использования. Свойство __proto__
раскрывает внутренний объекта. Лучше использовать
getPrototypeOf
, как показано ниже.
Мы можем провести сравнение с методами, перечисленными на сайте mdn. Итак, все массивы могут получить доступ к этим методам, но сами они не обладают ими в качестве свойств.
Эти методы может использовать каждый массив. Но вместо отдельного определения в каждом массиве методы определяются в объекте-прототипе. Это относится не только к массивам, но и к другим встроенным объектам в JavaScript. Эту особенность можно применять и при создании объектов.
Как это возможно?
Объект, который является значением свойства , — это прототип для того объекта, который мы рассматриваем. Если он не существует, его значение будет равно
null
. Когда мы используем метод pop
на ourArray
, интерпретатор JavaScript сначала будет искать этот метод в ourArray
. Если он не найдет его там, то будет искать в прототипе.
Этот процесс называется наследованием прототипа или цепочкой прототипов. Его можно продолжать до бесконечности, но лучше не создавать сложную цепочку, потому что отладка может привести к путанице. Использование цепочки прототипов помогает хранить функцию только в одном объекте, и интерпретатор будет искать ее именно в нем, если не найдет в первом объекте. Возьмем для примера код.
function GameTracker(name, result) {
this.name = name;
this.result = result;
}
В приведенном выше коде мы объявляем функцию-конструктор под названием GameTracker
с параметрами name
и result
. Если эта функция хранится в памяти, она хранится как функция с объектом.
У объекта есть свойство prototype
, которое представляет собой пустой объект. Любые функции, которые создаются с помощью конструктора GameTracker
, будут иметь доступ к свойству prototype
. С его помощью внутри функции мы устанавливаем параметры name
и result
, чтобы они ссылались на текущий экземпляр объекта.
let playerOne = new GameTracker("Fred", 8);
Далее мы создаем переменную playerOne
, изначально неинициализированную. Затем с помощью ключевого слова new
создается новый экземпляр GameTracker
. Когда мы используем ключевое слово new
, устанавливается свойство prototype
, которое будет ссылкой на объект GameTracker
. Если мы рассмотрим переменную playerOne
, то увидим следующее:
console.log(playerOne);
//Возвращает ---> GameTracker {name: 'Fred', result: 8}
Теперь попробуем определиться с конструктором playerOne
:
Object.getPrototypeOf(playerOne);
//Возвращает ---> constructor: ? GameTracker(name, result)
Используя прототип, можно добавить метод к прототипу. Добавим метод для запуска игры:
GameTracker.prototype.start = function() {
return `Hello ${this.name} the game is ready.`;
}
Теперь снова посмотрим на объект playerOne
. Мы видим, что функция start
хранится в прототипе.
Мы можем использовать функцию start
для playerOne
. Попробуем это сделать:
playerOne.start();
//Возвращает ---> 'Hello Fred the game is ready.'