Когда мы создаем пустой объект в JavaScript, он выглядит пустым.
const geralt = {};
Мы не установили никаких свойств для этого объекта, поэтому это вызовет ошибку:
console.log( geralt.fight() );
Однако это не приведет к ошибке:
console.log( geralt.hasOwnProperty() );
Кажется, что метод hasOwnProperty()
действительно существует, хотя мы его и не определили.
От куда это?
Тайные свойства объектов
Когда мы создаем объект, JavaScript автоматически добавляет к нему невидимое свойство. Это свойство __proto__
.
Это довольно уродливое имя, поэтому его обычно произносят как prototype. Давайте проверим это:
console.log( geralt.__proto__ ); // → {...}
Помните, что прототип — это просто произношение __proto__
, а не фактическое имя, так что это не работает:
console.log( geralt.prototype ); // → undefined
Объект.прототип
Мы установили, что JavaScript добавляет это секретное свойство к нашему объекту. Но что это на самом деле? Ну, __proto__
— это просто объект со своими свойствами по умолчанию.
Чтобы немного вернуться назад, Object
— это функция-конструктор, которая ссылается на все объекты, созданные в документе. Мы используем Object.prototype
для добавления свойств или методов ко всем объектам, которые наследуются от Object
, а __proto__
на самом деле является ссылкой на этот стандартный прототип всех объектов.
Вот почему любой созданный вами пользовательский объект может получить доступ к файлу hasOwnProperty()
.
Если мы немного упростим, массивы и функции также являются объектами в JavaScript, и у них есть свои собственные прототипы по умолчанию, Array.prototype
и Function.prototype
соответственно.
console.log(Object.getPrototypeOf({}) === Object.prototype); // → true console.log(Object.getPrototypeOf(function () {}) === Function.prototype); // → true console.log(Object.getPrototypeOf([]) === Array.prototype); // → true
Свойства прототипа
А вот и самое интересное. Когда мы пытаемся получить доступ к свойству, которого нет у нашего объекта, вместо этого ищется прототип объекта, чтобы проверить, существует ли такое свойство хотя бы там.
Вернемся к первому примеру, когда мы вызвали hasOwnProperty()
для нашего пустого объекта geralt
, вот что произошло:
- браузер видит, что
geralt.hasOwnProperty()
не существует - так это выглядит в
geralt.__proto__
- обнаруживает, что
geralt.__proto__.hasOwnProperty()
существует, и вызывает его
Прототип полон сюрпризов. Например, помимо hasOwnProperty()
у него есть еще несколько свойств. Например:
isPrototypeOf()
— можем проверить родителя прототипа.propertyIsEnumerable()
— мы можем проверить, можно ли запустить цикл на свойстве.toLocaleString()
— мы можем форматировать даты.toString()
— мы можем представить объект простой строкой.__proto__
— ПОДОЖДИТЕ ЧТО?
Да, действительно, в __proto__
есть еще один объект __proto__
.
Сеть прототипов
Поскольку прототип — это объект (а у каждого объекта есть прототип), у нашего прототипа тоже есть свой прототип. Это называется цепочкой прототипов. Цепочка заканчивается, когда мы достигаем прототипа, который имеет нуль для своего собственного прототипа:
geralt.__proto__.__proto__ // → null
Это конец цепочки, в которой:
- геральт наш пользовательский объект
- первый
__proto__
является ссылкой наObject.prototype
и наследует все его свойства - второй
__proto__
равенnull
и, таким образом, завершает цепочку
Мы можем создавать объекты с пользовательскими прототипами. Другими словами, мы можем добавить ссылки в цепочку.
Сначала мы создаем вспомогательный объект:
const schoolOfTheWolf = { rest: function(witcherName) { console.log(`The witcher ${witcherName} rests at Kaer Morhen.`) } }
Теперь мы можем использовать этот вспомогательный объект в качестве прототипа для нашего основного объекта со специальным ключевым словом Object.create
, например так:
const witcher = Object.create(schoolOfTheWolf); witcher.rest("Geralt"); // → The witcher Geralt rests at Kaer Morhen.
Теперь цепочка прототипов будет выглядеть так:
witcher.__proto__.__proto__.__proto__
В этой расширенной цепочке:
- ведьмак наш пользовательский объект
- первый
__proto__
является ссылкой наschoolOfTheWolf
и наследует все его свойства - второй
__proto__
является ссылкой наObject.prototype
и наследует все его свойства - третий
__proto__
равенnull
и, таким образом, завершает цепочку
Теперь вы понимаете, почему следующие две строки кода работают и по сути одинаковы:
witcher.rest("Geralt"); // → The witcher Geralt rests at Kaer Morhen. witcher.__proto__.rest("Geralt") // → The witcher Geralt rests at Kaer Morhen.
И почему мы можем вызвать hasOwnProperty("rest")
на первом прототипе и получить положительный результат.
console.log(witcher.__proto__.hasOwnProperty("rest")) // → true
Заключение
Мы узнали о секретных свойствах объектов, исходящих из прототипов. Мы увидели цепочку прототипов и добавили в нее свою ссылку.
Если эта статья оказалась для вас полезной, нажмите кнопку хлопать в ладоши 👏. Кроме того, не стесняйтесь комментировать! Буду рад помочь :)