Прототипы и наследование JavaScript
JavaScript использует модель прототипного наследования, которая существенно отличается от классовой модели, применяемой во многих других языках. В этом разделе мы проведем базовый обзор того, как это работает, — этого должно быть достаточно, чтобы понимать следующие материалы об Что такое загрязнение прототипов (prototype pollution).
Что такое объект в JavaScript?
Объект JavaScript — это, по сути, коллекция пар key:value, называемых «свойствами». Например, следующий объект может представлять пользователя:
const user = {
username: "wiener",
userId: 01234,
isAdmin: false
}Вы можете получить доступ к свойствам объекта, используя точечную нотацию или квадратные скобки для обращения к соответствующим ключам:
user.username // "wiener"
user['userId'] // 01234Помимо данных, свойства могут содержать исполняемые функции. В этом случае функция называется «методом».
const user = {
username: "wiener",
userId: 01234,
exampleMethod: function(){
// do something
}
}Пример выше — это «литерал объекта», то есть он создан с помощью синтаксиса фигурных скобок с явным объявлением свойств и их начальных значений. Однако важно понимать, что почти всё в JavaScript «под капотом» является объектом. В этих материалах термин «объект» относится ко всем сущностям, а не только к литералам объектов.
Что такое прототип в JavaScript?
Каждый объект в JavaScript связан с другим объектом, называемым его прототипом. По умолчанию JavaScript автоматически назначает новым объектам один из встроенных прототипов. Например, строкам автоматически назначается встроенный String.prototype. Ниже приведены ещё некоторые примеры этих глобальных прототипов:
Объекты автоматически наследуют все свойства своего назначенного прототипа, если у них нет собственного свойства с таким же ключом. Это позволяет разработчикам создавать новые объекты, которые могут переиспользовать свойства и методы существующих объектов.
Встроенные прототипы предоставляют полезные свойства и методы для работы с базовыми типами данных. Например, у объекта String.prototype есть метод toLowerCase(). В результате все строки автоматически имеют готовый метод для преобразования в нижний регистр. Это избавляет разработчиков от необходимости вручную добавлять такое поведение к каждой новой строке.
Как работает наследование объектов в JavaScript?
Всякий раз, когда вы обращаетесь к свойству объекта, движок JavaScript сначала пытается получить доступ непосредственно к самому объекту. Если у объекта нет соответствующего свойства, движок JavaScript ищет его в прототипе объекта. С учётом следующих объектов это позволяет, например, обращаться к myObject.propertyA:
Вы можете увидеть это поведение в действии в консоли браузера. Сначала создайте полностью пустой объект:
Затем введите myObject, поставьте точку. Обратите внимание, что консоль предлагает выбрать из списка свойств и методов:

Хотя для самого объекта не определено никаких свойств или методов, он унаследовал некоторые из встроенного Object.prototype.
Цепочка прототипов
Обратите внимание, что прототип объекта — это просто другой объект, у которого тоже должен быть свой прототип, и так далее. Поскольку практически всё в JavaScript «под капотом» является объектом, эта цепочка в конечном счёте ведёт к верхнеуровневому Object.prototype, прототип которого — просто null.
Ключевой момент: объекты наследуют свойства не только от своего непосредственного прототипа, но и от всех объектов выше по цепочке прототипов. В примере выше это означает, что объект username имеет доступ к свойствам и методам как String.prototype, так и Object.prototype.
Доступ к прототипу объекта с помощью __proto__
У каждого объекта есть специальное свойство, с помощью которого можно получить доступ к его прототипу. Хотя это свойство формально не стандартизировано, __proto__ — де-факто стандарт, используемый большинством браузеров. Если вы знакомы с объектно-ориентированными языками, это свойство выступает и как геттер, и как сеттер прототипа объекта. То есть вы можете использовать его для чтения прототипа и его свойств, а при необходимости — даже переназначать их.
Как и к любому свойству, к __proto__ можно обратиться через точку или через квадратные скобки:
Вы даже можете «сцеплять» обращения к __proto__, поднимаясь по цепочке прототипов:
Изменение прототипов
Хотя это обычно считается плохой практикой, встроенные прототипы JavaScript можно изменять так же, как и любые другие объекты. Это означает, что разработчики могут кастомизировать или переопределять поведение встроенных методов и даже добавлять новые методы для выполнения полезных операций.
Например, в современном JavaScript есть метод trim() для строк, позволяющий легко удалить начальные и конечные пробелы. До появления этого встроенного метода разработчики иногда добавляли собственную реализацию такого поведения в объект String.prototype, делая примерно следующее:
Благодаря прототипному наследованию все строки получали доступ к этому методу:
Что дальше?
Last updated