Прототип объекта
Материал на этой странице устарел, поэтому скрыт из оглавления сайта.
Более новая информация по этой теме находится на странице https://learn.javascript.ru/prototype-inheritance.
Объекты в JavaScript можно организовать в цепочки так, чтобы свойство, не найденное в одном объекте, автоматически искалось бы в другом.
Связующим звеном выступает специальное свойство __proto__ .
Прототип proto
Если один объект имеет специальную ссылку __proto__ на другой объект, то при чтении свойства из него, если свойство отсутствует в самом объекте, оно ищется в объекте __proto__ .
Свойство __proto__ доступно во всех браузерах, кроме IE10-, а в более старых IE оно, конечно же, тоже есть, но напрямую к нему не обратиться, требуются чуть более сложные способы, которые мы рассмотрим позднее.
Пример кода (кроме IE10-):
- Первый alert здесь работает очевидным образом – он выводит свойство jumps объекта rabbit .
- Второй alert хочет вывести rabbit.eats , ищет его в самом объекте rabbit , не находит – и продолжает поиск в объекте rabbit.__proto__ , то есть, в данном случае, в animal .
Иллюстрация происходящего при чтении rabbit.eats (поиск идёт снизу вверх):

Объект, на который указывает ссылка __proto__ , называется «прототипом». В данном случае получилось, что animal является прототипом для rabbit .
Также говорят, что объект rabbit «прототипно наследует» от animal .
Обратим внимание – прототип используется исключительно при чтении. Запись значения, например, rabbit.eats = value или удаление delete rabbit.eats – работает напрямую с объектом.
В примере ниже мы записываем свойство в сам rabbit , после чего alert перестаёт брать его у прототипа, а берёт уже из самого объекта:
Другими словами, прототип – это «резервное хранилище свойств и методов» объекта, автоматически используемое при поиске.
У объекта, который является __proto__ , может быть свой __proto__ , у того – свой, и так далее. При этом свойства будут искаться по цепочке.
Если вы будете читать спецификацию ECMAScript – свойство __proto__ обозначено в ней как [[Prototype]] .
Двойные квадратные скобки здесь важны, чтобы не перепутать его с совсем другим свойством, которое называется prototype , и которое мы рассмотрим позже.
Метод hasOwnProperty
Обычный цикл for..in не делает различия между свойствами объекта и его прототипа.
Он перебирает всё, например:
Иногда хочется посмотреть, что находится именно в самом объекте, а не в прототипе.
Вызов obj.hasOwnProperty(prop) возвращает true , если свойство prop принадлежит самому объекту obj , иначе false .
Для того, чтобы перебрать свойства самого объекта, достаточно профильтровать key через hasOwnProperty :
Object.create(null)
Зачастую объекты используют для хранения произвольных значений по ключу, как коллекцию:
При дальнейшем поиске в этой коллекции мы найдём не только text и age , но и встроенные функции:
Это может быть неприятным сюрпризом и приводить к ошибкам, если названия свойств приходят от посетителя и могут быть произвольными.
Чтобы этого избежать, мы можем исключать свойства, не принадлежащие самому объекту:
Однако, есть путь и проще:
Объект, создаваемый при помощи Object.create(null) не имеет прототипа, а значит в нём нет лишних свойств. Для коллекции – как раз то, что надо.
Методы для работы с proto
В современных браузерах есть два дополнительных метода для работы с __proto__ . Зачем они нужны, если есть __proto__ ? В общем-то, не очень нужны, но по историческим причинам тоже существуют.
Чтение: Object.getPrototypeOf(obj) Возвращает obj.__proto__ (кроме IE8-) Запись: Object.setPrototypeOf(obj, proto) Устанавливает obj.__proto__ = proto (кроме IE10-).
Кроме того, есть ещё один вспомогательный метод:
Создание объекта с прототипом: Object.create(proto, descriptors) Создаёт пустой объект с __proto__ , равным первому аргументу (кроме IE8-), второй необязательный аргумент может содержать дескрипторы свойств.
Итого
- В JavaScript есть встроенное «наследование» между объектами при помощи специального свойства __proto__ .
- При установке свойства rabbit.__proto__ = animal говорят, что объект animal будет «прототипом» rabbit .
- При чтении свойства из объекта, если его в нём нет, оно ищется в __proto__ . Прототип задействуется только при чтении свойства. Операции присвоения obj.prop = или удаления delete obj.prop совершаются всегда над самим объектом obj .
Несколько прототипов одному объекту присвоить нельзя, но можно организовать объекты в цепочку, когда один объект ссылается на другой при помощи __proto__ , тот ссылается на третий, и так далее.
В современных браузерах есть методы для работы с прототипом:
-
(кроме IE8-) (кроме IE10-) (кроме IE8-)
Возможно, вас смущает недостаточная поддержка __proto__ в старых IE. Но это не страшно. В последующих главах мы рассмотрим дополнительные методы работы с __proto__ , включая те, которые работают везде.
Также мы рассмотрим, как свойство __proto__ используется внутри самого языка JavaScript и как организовать классы с его помощью.
Прототипы объектов в JS
Прежде чем изучать прототипы, убедитесь, что вы хорошо ознакомлены со следующими темами.
- Объекты в JS
- Конструктор объектов в JS
Если вы не знакомы с этими темами— у нас есть соответствующие статьи.
Что надо знать
В JavaScript объект можно создать с помощью функции-конструктора. Например,
Прототипы объектов
У каждой функции и объекта по умолчанию есть свойство с именем prototype . Например,
В этом примере мы пытаемся получить доступ к свойству prototype функции-конструктора Person .
Поскольку свойство prototype в данный момент не имеет значения, оно показывает пустой объект < . >.
Наследование с помощью prototype
В JavaScript прототипы можно использовать для добавления свойств и методов в функцию-конструктор. Объекты наследуют свойства и методы от прототипа. Например,
Вывод:
В этом примере мы добавили новое свойство gender в функцию-конструктор Person следующим образом:
Как видно из вывода, объекты person1 и person2 наследуют свойство gender из свойства prototype функции конструктора Person .
Следовательно, из обоих объектов person1 и person2 можно получить доступ к свойству gender .
Примечание. Синтаксис для добавления свойства в функцию конструктора объекта следующий:
Прототипы используют для предоставления дополнительных свойств всем объектам, созданным из функции конструктора.
Добавление методов в функцию-конструктор с помощью prototype
В функцию-конструктор с помощью prototype можно добавлять не только новые свойства, но и методы. Например,
В этом примере мы к функции конструктора Person с помощью prototype добавили новый метод greet .
Изменение прототипа
Если изменить значение прототипа, то оно изменится у всех новых объектов. У всех ранее созданных объектов сохранится прежнее значение. Например,
Совет. Не стоит изменять прототипы стандартных встроенных объектов JavaScript, таких как строки, массивы и т.п.
Цепочка прототипов
Если объект попытается получить доступ к одному и тому же свойству, которое находится в функции конструктора и в объекте-прототипе, объект возьмет свойство из функции конструктора. Например,
В приведенном выше примере свойство name объявлено в функции-конструкторе, а также в свойстве prototype .
- person1.name ищет свойство name в функции-конструкторе. Поскольку в функции-конструкторе есть свойство name (со значением ‘Андрей’ ), объект берет значение из этого свойства.
- person1.age ищет свойство age в функции-конструкторе. Поскольку в функции-конструкторе нет свойства age , программа просматривает объект-прототип функции-конструктора, и объект наследует свойство от объекта-прототипа (если оно доступно).
Примечание. Получить доступ к свойству prototype функции-конструктора можно и из объекта.
В этом примере объект person доступается к свойству prototype с помощью __proto__. На самом деле, такой подход уже устарел и по возможности лучше избегать его.
Прототипы объектов
JavaScript часто описывают как язык прототипного наследования — каждый объект, имеет объект-прототип, который выступает как шаблон, от которого объект наследует методы и свойства. Объект-прототип так же может иметь свой прототип и наследовать его свойства и методы и так далее. Это часто называется цепочкой прототипов и объясняет почему одним объектам доступны свойства и методы которые определены в других объектах.
Точнее, свойства и методы определяются в свойстве prototype функции-конструктора объектов, а не в самих объектах.
В JavaScript создаётся связь между экземпляром объекта и его прототипом (свойство __proto__ , которое является производным от свойства prototype конструктора), а свойства и методы обнаруживаются при переходе по цепочке прототипов.
Примечание: Важно понимать, что существует различие между прототипом объекта (который доступен через Object.getPrototypeOf(obj) или через устаревшее свойство __proto__ ) и свойством prototype в функциях-конструкторах. Первое свойство является свойством каждого экземпляра, а второе — свойством конструктора. То есть Object.getPrototypeOf(new Foobar()) относится к тому же объекту, что и Foobar.prototype .
Давайте посмотрим на пример, чтобы стало понятнее.
Понимание прототипа объектов
Вернёмся к примеру, когда мы закончили писать наш конструктор Person() — загрузите пример в свой браузер. Если у вас ещё нет работы от последней статьи, используйте наш пример oojs-class-further-exercises.html (см. Также исходный код).
В этом примере мы определили конструктору функцию, например:
Затем мы создаём экземпляр объекта следующим образом:
Если вы наберёте « person1. » в вашей консоли JavaScript, вы должны увидеть, что браузер пытается автоматически заполнить это с именами участников, доступных на этом объекте:

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

Итак, что произойдёт, если вы вызываете метод в person1 , который фактически определён в Object ? Например:
Этот метод — Object.valueOf() наследуется person1 , потому что его конструктором является Person() , а прототипом Person() является Object() . valueOf() возвращает значение вызываемого объекта — попробуйте и убедитесь! В этом случае происходит следующее:
- Сначала браузер проверяет, имеет ли объект person1 доступный в нем метод valueOf() , как определено в его конструкторе Person() .
- Это не так, поэтому следующим шагом браузер проверяет, имеет ли прототип объекта ( Object() ) конструктора Person() доступный в нем метод valueOf() . Так оно и есть, поэтому он вызывается, и все хорошо!
Примечание: Мы хотим повторить, что методы и свойства не копируются из одного объекта в другой в цепочке прототипов — к ним обращаются, поднимаясь по цепочке, как описано выше.
Примечание: Официально нет способа получить доступ к объекту прототипа объекта напрямую — «ссылки» между элементами в цепочке определены во внутреннем свойстве, называемом [[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 , включая новые экземпляры объектов, созданные из конструктора Person() .
Object.is() , Object.keys() и другие члены, не определённые в контейнере prototype , не наследуются экземплярами объектов или типами объектов, которые наследуются от Object.prototype . Это методы / свойства, доступные только в конструкторе Object() .
Примечание: Это кажется странным — как у вас есть метод, определённый для конструктора, который сам по себе является функцией? Ну, функция также является типом объекта — см. Ссылку на конструктор Function() , если вы нам не верите.
- Вы можете проверить существующие свойства прототипа для себя — вернитесь к нашему предыдущему примеру и попробуйте ввести следующее в консоль JavaScript:
Вы увидите большое количество методов, определённых для свойства prototype Object ‘а , которые затем доступны для объектов, которые наследуются от Object , как показано выше.
Вы увидите другие примеры наследования цепочек прототипов по всему JavaScript — попробуйте найти методы и свойства, определённые на прототипе глобальных объектов String , Date , Number и Array , например. Все они имеют несколько элементов, определённых на их прототипе, поэтому, например, когда вы создаёте строку, вот так:
В myString сразу есть множество полезных методов, таких как split() , indexOf() , replace() и т. д.
Предупреждение: Важно: Свойство prototype является одной из наиболее противоречивых названий частей JavaScript — вы можете подумать, что this указывает на объект прототипа текущего объекта, но это не так (это внутренний объект, к которому можно получить доступ __proto__ , помните ?). prototype вместо этого — свойство, содержащее объект, на котором вы определяете членов, которые вы хотите наследовать.
Снова create()
Ранее мы показали, как метод Object.create() может использоваться для создания нового экземпляра объекта.
-
Например, попробуйте это в консоли JavaScript предыдущего примера:
Это вернёт объект person1.
Свойство constructor
Каждая функция-конструктор имеет свойство prototype , значением которого является объект, содержащий свойство constructor . Это свойство constructor указывает на исходную функцию-конструктор. Как вы увидите в следующем разделе, свойства, определённые в свойстве Person.prototype (или в общем случае в качестве свойства прототипа функции конструктора, который является объектом, как указано в предыдущем разделе) становятся доступными для всех объектов экземпляра, созданных с помощью конструктор Person() . Следовательно, свойство конструктора также доступно для объектов person1 и person2 .
-
Например, попробуйте эти команды в консоли:
Это хорошо работает. Вам не нужно будет использовать его часто, но это может быть действительно полезно, если вы хотите создать новый экземпляр и не имеете ссылки на исходный конструктор, который легко доступен по какой-либо причине.
Свойство constructor имеет другие применения. Например, если у вас есть экземпляр объекта и вы хотите вернуть имя конструктора этого экземпляра, вы можете использовать следующее:
Например, попробуйте это:
Примечание: Значение constructor.name может измениться (из-за прототипического наследования, привязки, препроцессоров, транспилеров и т. д.), Поэтому для более сложных примеров вы захотите использовать оператор instanceof .
Изменение прототипов
Давайте рассмотрим пример изменения свойства prototype функции-конструктора — методы, добавленные в прототип, затем доступны для всех экземпляров объектов, созданных из конструктора.
-
Вернитесь к нашему примеру oojs-class-further-exercises.html и создайте локальную копию исходного кода. Ниже существующего JavaScript добавьте следующий код, который добавляет новый метод в свойство prototype конструктора:
Должно появиться всплывающее окно, с именем пользователя, определённым в конструкторе. Это действительно полезно, но ещё более полезно то, что вся цепочка наследования обновляется динамически, автоматически делая этот новый метод доступным для всех экземпляров объектов, полученных из конструктора.
Подумайте об этом на мгновение. В нашем коде мы определяем конструктор, затем мы создаём экземпляр объекта из конструктора, затем добавляем новый метод к прототипу конструктора:
Но метод farewell() по-прежнему доступен в экземпляре объекта person1 — его элементы были автоматически обновлены, чтобы включить недавно определённый метод farewell() .
Примечание: Если у вас возникли проблемы с получением этого примера для работы, посмотрите на наш пример oojs-class-prototype.html (см. также это running live).
Вы редко увидите свойства, определённые в свойстве prototype , потому что они не очень гибки при таком определении. Например, вы можете добавить свойство следующим образом:
Это не очень гибко, так как человека нельзя назвать так. Было бы намного лучше сделать это, создав fullName из name.first и name.last :
Однако это не работает, поскольку в этом случае this будет ссылаться на глобальную область, а не на область функции. Вызов этого свойства вернёт undefined undefined . Это отлично работало с методом, который мы определили ранее в прототипе, потому что он находится внутри области функций, которая будет успешно перенесена в область экземпляра объекта. Таким образом, вы можете определить постоянные свойства прототипа (т. е. те, которые никогда не нуждаются в изменении), но обычно лучше определять свойства внутри конструктора.
Фактически, довольно распространённый шаблон для большего количества определений объектов — это определение свойств внутри конструктора и методов в прототипе. Это упрощает чтение кода, поскольку конструктор содержит только определения свойств, а методы разделены на отдельные блоки. Например:
Этот образец можно увидеть в действии в примере приложения плана школы Петра Залевы.
Резюме
В этой статье рассмотрены прототипы объектов JavaScript (в том числе и то, как прототип цепочки объектов позволяет объектам наследовать функции друг от друга), свойство прототипа и как его можно использовать для добавления методов к конструкторам и другие связанные с этой статьёй темы.
В следующей статье мы рассмотрим то, как вы можете реализовать наследование функциональности между двумя собственными настраиваемыми объектами.
Прототипы в JS и малоизвестные факты
Получив в очередной раз кучу вопросов про прототипы на очередном собеседовании, я понял, что слегка подзабыл тонкости работы прототипов, и решил освежить знания. Я наткнулся на кучу статей, которые были написаны либо по наитию автора, как он «чувствует» прототипы, либо статья была про отдельную часть темы и не давала полной картины происходящего.
Оказалось, что есть много неочевидных вещей из старых времён ES5 и даже ES6, о которых я не слышал. А еще оказалось, что вывод консоли браузера может не соответствовать действительности.
Что такое прототип
Объект в JS имеет собственные и унаследованные свойства, например, в этом коде:
у объекта foo имеется собственное свойство bar со значением 1 , но также имеются и другие свойства, такие как toString . Чтобы понять, как объект foo получает новое свойство toString , посмотрим на то, из чего состоит объект:
Дело в том, что у объекта есть ссылка на другой объект-прототип. При доступе к полю foo.toString сначала выполняется поиск такого свойства у самого объекта, а потом у его прототипа, прототипа его прототипа, и так пока цепочка прототипов не закончится. Это похоже на односвязный список объектов, где поочередно проверяется объект и его объекты-прототипы. Так реализовано наследование свойств, например, у (почти, но об этом позже) любого объекта есть методы valueOf и toString .
Как выглядит прототип
У всех прототипов имеются два общих свойства, constructor и __proto__ . Свойство constructor указывает на функцию-конструктор, с помощью которой создавался объект, а свойство __proto__ указывает на следующий прототип в цепочке (либо null, если это последний прототип). Остальные свойства доступны через . , как в примере выше.
Да кто такой этот ваш constructor
constructor – это ссылка на функцию, с помощью которой был создан объект:
Не совсем понятна идея зачем он был нужен, возможно, как способ клонирования объекта:
Но я не нашел подходящий пример его использования, если у Вас есть примеры проектов, где это использовалось, то напишите об этом. В остальном же использовать constructor лучше не стоит, так как это writable свойство, которое можно случайно перезаписать, работая с прототипом, и сломать часть логики.
Где живёт прототип
На самом деле, объекты представляют собой не только поля, доступные для JS кода. Интерпретатор также сохраняет некоторые приватные данные объекта для работы с ним, для этого в стандарте определено понятие внутренних слотов, которые обозначены как имя в квадратных скобках [[SlotName]] . Для прототипов отведен приватный слот [[Prototype]] содержащий ссылку на объект-прототип (либо null , если прототипа нет).
Из-за того, что [[Prototype]] предназначался исключительно для самого JS движка, получить доступ к прототипу объекта было невозможно. Для случаев когда это было нужно, ввели нестандартное свойство __proto__ , которое поддержали многие браузеры и которое по итогу попало в сам стандарт, но как опциональное и стандартизированное только для обратной совместимости с существующим JS кодом.
О чем вам недоговаривает дебаггер, или он вам не прототип
Свойство __proto__ является геттером и сеттером для внутреннего слота [[Prototype]] и находится в Object.prototype :
Из-за этого я избегал записи __proto__ для обозначения прототипа. __proto__ находится не в самом объекте, что приводит к неожиданным результатам. Для демонстрации попробуем через __proto__ удалить прототип объекта и затем восстановить его:
Как так получилось? Дело в том, что __proto__ – это унаследованное свойство Object.prototype , а не самого объекта foo . Из-за этого в момент когда в цепочке прототипов пропадает ссылка на Object.prototype , __proto__ превращается в тыкву и перестает работать с прототипом.
А теперь отработаем кликбейт из введения. Представим следующую цепочку прототипов:

В консоли Chrome foo будет выглядеть следующим образом:
А теперь уберем связь между baz и Object.prototype :
И теперь в консоли Chrome видим следующий результат:
Связь с Object.prototype разорвана у baz и __proto__ возвращает undefined даже у дочернего объекта foo , однако Chrome все равно показывает что __proto__ есть. Скорее всего тут имеется в виду внутренний слот [[Prototype]] , но для простоты это было изменено на __proto__ , ведь если не извращаться с цепочкой прототипов, это будет верно.
Как работать с прототипом объекта
Рассмотрим основные способы работы с прототипом: изменение прототипа и создание нового объекта с указанным прототипом.
Для изменения прототипа у существующего объекта есть всего два метода: использование сеттера __proto__ и метод Object.setPrototypeOf .
Если браузер не поддерживает ни один из этих методов, то изменить прототип объекта невозможно, можно только создать его копию с новым прототипом.
Но есть один нюанс с внутренним слотом [[Extensible]] который указывает на то, возможно ли добавлять к нему новые поля и менять его прототип. Есть несколько функций, которые выставляют этот флаг в false и предотвращают смену прототипа: Object.freeze , Object.seal , Object.preventExtensions . Пример:
А теперь менее категоричный вопрос создания нового объекта с прототипом. Для этого есть следующие способы.
Стандартный способ:
Если нет поддержки Object.create , но есть __proto__ :
И в случае если отсутствует поддержка всего вышеперечисленного:
Способ основан на логике работы оператора new , о которой поговорим чуть ниже. Но сам способ основан на том, что оператор new берет свойство prototype функции и использует его в качестве прототипа, т.е. устанавливает объект в [[Prototype]] , что нам и нужно.
Функции и конструкторы
А теперь поговорим про функции и как они работают в качестве конструкторов.
Функция Person тут является конструктором и создает два поля в новом объекте, а цепочка прототипов выглядит так:
Откуда взялся Person.prototype ? При объявлении функции, у нее автоматически создается свойство prototype для того чтобы ее можно было использовать как конструктор (note 3), таким образом свойство prototype функции не имеет отношения к прототипу самой функции, а задает прототипы для дочерних объектов. Это позволит реализовывать наследование и добавлять новые методы, например так:
И теперь вызов user.fullName() вернет строку «John Doe».
Что такое new
На самом деле оператор new не таит в себе никакой магии. При вызове new выполняет несколько действий:
- Создает новый объект self
- Записывает свойство prototype функции конструктора в прототип объекта self
- Вызывает функцию конструктор с объектом self в качестве аргумента this
- Возвращает self если конструктор вернул примитивное значение, иначе возвращает значение из конструктора
Все эти действия можно сделать силами самого языка, поэтому можно написать свой собственный оператор new в виде функции:
Но начиная с ES6 волшебство пришло и к new в виде свойства new.target, которое позволяет определить, была ли вызвана функция как конструктор с new, или как обычная функция:
new.target будет undefined для обычного вызова функции, и ссылкой на саму функцию в случае вызова через new ;
Наследование
Зная все вышеперечисленное, можно сделать классическое наследование дочернего класса Student от класса Person . Для этого нужно
- Создать конструктор Student с вызовом логики конструктора Person
- Задать объекту `Student.prototype` прототип от `Person`
- Добавить новые методы к `Student.prototype`
Фиолетовым цветом обозначены поля объекта (они все находятся в самом объекте, т.к. this у всей цепочки прототипов один), а методы желтым (находятся в прототипах соответствующих функций)
Вариант 1 предпочтительнее, т.к. Object.setPrototypeOf может привести к проблемам с производительностью.
Сколько вам сахара к классу
Для того чтобы облегчить классическую схему наследование и предоставить более привычный синтаксис, были представлены классы, просто сравним код с примерами Person и Student:
Уменьшился не только бойлерплейт, но и поддерживаемость:
- В отличие от функции конструктора, при вызове конструктора без new выпадет ошибка
- Родительский класс указывается ровно один раз при объявлении
При этом цепочка прототипов получается идентичной примеру с явным указанием prototype у функций конструкторов.
Наивно было бы ожидать, что одна статья ответит на все вопросы. Если у Вас есть интересные вопросы, экскурсы в историю, аргументированные или беспочвенные заявления о том, что я сделал все не так, либо правки по ошибкам, пишите в комментарии.
P. P. S.
К сожалению главный кликбейт статьи перестал быть актуальным. В данный момент Chrome (версия 93, на момент обновления статьи) перестал использовать __proto__ для обозначения прототипа, и теперь отображает его как слот [[Prototype]] :

Справедливости ради хочу отметить что в Firefox (92) также не используется обозначение __proto__ :