W3docs

Методы прототипов JavaScript без __proto__

Современные методы Object.create(), Object.getPrototypeOf() и Object.setPrototypeOf() заменяют устаревший __proto__ в JavaScript.

Каждый объект JavaScript связан с другим объектом, называемым его прототипом, и поиск свойств выполняется по этой цепочке. Старым способом чтения или изменения этой связи было специальное свойство __proto__, но оно имеет реальные проблемы: это унаследованный акцессор (а не обычный слот данных), он ведёт себя непоследовательно в качестве ключа объекта, и его можно использовать для повреждения объектов. Современный JavaScript заменяет его явными, предсказуемыми методами конструктора Object.

В этой главе рассматриваются стандартные методы прототипов — Object.create(), Object.getPrototypeOf() и Object.setPrototypeOf() — а также вспомогательные методы для инспекции: Object.keys(), Object.values(), Object.entries() и Object.hasOwn(). Затем рассматривается наиболее полезный случай — объект без прототипа: безопасный «словарь» для произвольных ключей.

Для более широкого понимания работы цепочки прототипов смотрите Прототипное наследование и не только.

Чтение и установка прототипа

Используйте Object.getPrototypeOf() для чтения прототипа объекта и Object.setPrototypeOf() для его изменения. Это стандартизированные замены для чтения и записи __proto__.

javascript— editable

Object.create(proto) создаёт совершенно новый объект, прототипом которого является именно proto. Это самый чистый способ создать объект с выбранным прототипом без использования __proto__ вообще.

Почему следует избегать __proto__

__proto__ — это геттер/сеттер, определённый на Object.prototype, а не свойство, которое живёт на вашем объекте. Это различие приводит к двум практическим проблемам:

  • Он может давать сбои или вести себя странно в качестве ключа данных. Поскольку obj.__proto__ = value запускает сеттер, вы не можете надёжно хранить ключ буквально с именем "__proto__" на обычном объекте — присвоение не-объекта молча игнорируется, а присвоение объекта меняет прототип вместо добавления ключа.
  • Это известная поверхность атаки («загрязнение прототипа»). Код, копирующий недоверенные ключи на объект, может быть обманут и записать в __proto__, загрязняя Object.prototype для всей программы.

Стандартизированные методы явно выражают намерение: getPrototypeOf/setPrototypeOf изменяют прототип, тогда как обычная запись свойств никогда его не затрагивает.

javascript— editable

Объекты без прототипа

Object.create(null) создаёт объект, прототип которого равен null. Он ничего не наследует — ни toString, ни hasOwnProperty, ни акцессор __proto__.

javascript— editable

Проблема словаря, которую это решает

Распространённый паттерн — использование простого объекта как отображения строковых ключей в значения. Проблема в том, что обычный {} уже наследует ключи от Object.prototype, поэтому предоставляемые пользователем ключи вроде "constructor", "toString" или "__proto__" конфликтуют с унаследованными именами и нарушают поиск значений.

javascript— editable

Объект с null-прототипом не имеет унаследованных ключей, поэтому каждый ключ ведёт себя именно так, как записано — включая "__proto__":

javascript— editable

Именно поэтому объекты с null-прототипом являются безопасными словарями для недоверенных ключей. (Встроенный Map — ещё один хороший вариант, позволяющий использовать не-строковые ключи.)

Добавление методов в объект с null-прототипом

Поскольку прототипа для наследования нет, методы назначаются непосредственно как собственные свойства.

javascript— editable

Итерация ключей, значений и записей

Object.keys(), Object.values() и Object.entries() возвращают массивы собственных перечисляемых свойств объекта. Принципиально важно, что они игнорируют цепочку прототипов, поэтому одинаково работают на обычных объектах и объектах с null-прототипом. Подробнее см. Object.keys, values, entries.

javascript— editable

Одна оговорка для объектов с null-прототипом: у них нет унаследованного toString, поэтому передача такого объекта напрямую в шаблонные литералы или String() вызовет ошибку. Используйте для итерации вспомогательные методы Object.* (выше), а не полагайтесь на автоматическое преобразование в string.

Проверка собственных свойств с помощью Object.hasOwn()

Для проверки, является ли ключ собственным свойством объекта (а не унаследованным), предпочитайте Object.hasOwn(). Это современная замена obj.hasOwnProperty(), которая работает даже на объектах с null-прототипом, у которых метод hasOwnProperty вообще отсутствует.

javascript— editable

Композиция с помощью Object.assign()

Когда вы хотите, чтобы один объект получил поведение нескольких других, композиция зачастую понятнее, чем единственная цепочка наследования: объект может иметь только один прототип, но Object.assign(target, ...sources) может объединить методы из множества источников, копируя их собственные перечисляемые свойства в целевой объект.

javascript— editable

Object.assign() также копирует данные в объект с null-прототипом, создавая составной словарь без унаследованной поверхности:

javascript— editable

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

javascript— editable

Для глубокого, независимого копирования используйте вместо этого structuredClone(source).

Лучшие практики

  • Используйте стандартизированные методы, а не __proto__. Прибегайте к Object.create(), Object.getPrototypeOf() и Object.setPrototypeOf(). Относитесь к __proto__ как к устаревшему акцессору, которого следует избегать в своём коде.
  • Избегайте изменения прототипов после создания. Установка выбранного прототипа при создании через Object.create(proto) чище и понятнее, чем последующее изменение через Object.setPrototypeOf().
  • Используйте Object.create(null) (или Map) для словарей с недоверенными ключами. Это устраняет унаследованные ключи и предотвращает неожиданное загрязнение прототипа.
  • Предпочитайте Object.hasOwn() методу obj.hasOwnProperty(). Он короче, безопаснее в вызове и работает на объектах с null-прототипом.
  • Используйте композицию через Object.assign(), когда вам нужно поведение из нескольких источников — просто помните, что это поверхностное копирование.

Для связанных материалов смотрите Методы объектов и «this» и Флаги и дескрипторы свойств.

Заключение

Современные методы прототипов Object.* дают вам явный, предсказуемый контроль над цепочкой прототипов, заменяя причудливый акцессор __proto__. Object.create(null) создаёт чистый объект без прототипа, идеально подходящий для словарей, а Object.keys/values/entries, Object.hasOwn() и Object.assign() позволяют безопасно инспектировать и составлять объекты независимо от их прототипа.

Практика

Практика
Какой вызов создаёт объект без прототипа, делая его безопасным словарём для произвольных ключей?
Какой вызов создаёт объект без прототипа, делая его безопасным словарём для произвольных ключей?
Was this page helpful?