Эта статья является первой в обширной серии из пяти частей о прототипном наследовании.
Часть 1 — Понимание цепочки прототипов
Часть 2 — Наследование прототипов с помощью Object.create
Часть 3 — Наследование прототипов с функциями конструктора
Часть 4 — Скоро!
Часть 5 — Скоро!
Когда я перешел с Java и C++ на JavaScript, я нашел концепцию прототипного наследования чрезвычайно запутанной. Я просто не мог уложиться в голове. Это слишком отличалось от того, к чему я привык.
Таким образом, я создал эту серию, чтобы помочь разработчикам, которые, как и я в прошлом, плохо знакомы с JavaScript и пытаются получить более глубокое понимание механики прототипов.
Чтобы убедиться, что вы полностью усвоили все концепции, представленные в этой серии, я привожу упражнения (и решения) в конце каждой части. Я настоятельно рекомендую пройти каждое упражнение.
Смело обращайтесь с вопросами! Буду рад вашим отзывам и любой конструктивной критике.
Часть 1. Понимание цепочки прототипов
В JavaScript каждый объект имеет специальное соединение, называемое [[prototype]], которое связывает его с другим объектом, известным как его прототип. Этот объект-прототип может иметь свой собственный прототип, образуя цепочку. Цепочка продолжается до тех пор, пока не достигнет объекта с нулевым прототипом.
Чтобы визуализировать это, представьте генеалогическое древо, где у каждого человека есть родитель, у которого, в свою очередь, есть свой родитель и так далее. Точно так же объекты в JavaScript имеют прототип, и этот прототип также может иметь свой собственный прототип.
Вот пример цепочки прототипов:
Большинство объектов JavaScript имеют прототип Object.prototype
. Однако есть определенные объекты, которые не наследуются напрямую от Object.prototype
. Например:
- Объекты, созданные с помощью
Object.create(someProto)
, имеют прототипsomeProto
. Я подробно расскажу оObject.create
во Части 2 этой серии. - Некоторые встроенные объекты в JavaScript, такие как
Array
,Function
,RegExp
иDate
, имеют собственные отличные прототипы (Array.prototype
,Function.prototype
,RegExp.prototype
иDate.prototype
соответственно). Хотя прототипы этих встроенных объектов могут в конечном итоге ссылаться наObject.prototype
, сами объекты не имеют прямого прототипаObject.prototype
.
При попытке доступа к свойству объекта поиск этого свойства выходит за пределы самого объекта. Он продолжается до прототипа объекта, прототипа прототипа и так далее, пока либо не будет обнаружено свойство с совпадающим именем, либо не будет достигнут конец цепочки прототипов. Если при поиске не удается найти нужное свойство, результатом операции будет undefined
для свойства-значения или TypeError
для свойства с функциональным значением.
Я проиллюстрирую изложенную выше концепцию, рассмотрев два примера:
Пример 1
const pumba = { name: 'Pumba', age: 15 } console.log(pumba.toString()) // [object object]
Когда мы объявляем объектный литерал, такой как pumba
во фрагменте выше, он автоматически получает Object.prototype
в качестве своего прототипа. По сути, наш небольшой фрагмент выше создал следующую цепочку прототипов:
Обратите внимание, что мы никогда не определяли toString
для pumba
, так откуда же это взялось? Когда мы попытались получить доступ к toString
, и он не был найден на pumba
, среда выполнения JavaScript проверила прототип pumba
, которым оказался Object.prototype
. Этот объект действительно имеет метод toString
.
На самом деле, если мы console.log(Object.prototype)
в консоли Chrome, мы видим метод toString
вместе с другими методами, с которыми вы, возможно, знакомы.
Пример 2
const arr = ['Pokity', 'Wokity', 'Lokity']; console.log(arr.at(1)); // Wokity
Как наш массив arr,
получил метод at
? Как упоминалось выше, ссылка [[prototype]] массива указывает на Array.prototype
. То есть приведенный выше код создает следующую цепочку прототипов:
Если мы рассмотрим объект Array.prototype
в консоли Chrome, мы увидим
Array.prototype
имеет метод at
.
Теперь, когда мы понимаем цепочку прототипов, возникает следующий естественный вопрос: как нам получить доступ к прототипу объекта obj
? Есть два основных способа:
Object.getPrototypeOf(obj)
obj.__proto__
const newObj = { key: 'value' } console.log(newObj.__proto__ === Object.prototype); // true console.log(Object.getPrototypeOf(newObj) === Object.prototype); // true
Мы будем придерживаться Object.getPrototypeOf
, так как использование __proto__
больше не рекомендуется. Подробнее о том, почему, смотрите здесь.
К этому моменту у вас должно быть хорошее представление о том, как работает цепочка прототипов. Давайте продолжим и обсудим три основных способа создания цепочки прототипов:
- Объект.создать
- Функции конструктора
- Конструкторы синтаксиса класса
Перейдите к Часть 2, Наследование прототипов с помощью Object.create.
Упражнения
Упражнение 1
const myBirthday = new Date('December 11 1986'); console.log(myBirthday.getFullYear()); // 1986
Мы никогда не определяли функцию getFullYear
для myBirthday
.
На самом деле оператор myBirthday.hasOwnProperty(‘getFullYear’)
оценивается как false
.
Почему мы не получаем TypeError
при запуске приведенного выше кода?
Решение
Объекты Date
имеют прототип Date.prototype
. Таким образом, мы имеем следующую цепочку прототипов:
null ← Object.prototype ← Date.prototype ← myBirthday
Вы можете убедиться в этом, выполнив следующий код
console.log(Object.getPrototypeOf(myBirthday) === Date.prototype); console.log(Object.getPrototypeOf(Date.prototype) === Object.prototype); console.log(Object.getPrototypeOf(Object.prototype) === null);
Date.prototype имеет метод getFullYear. Оператор Date.prototype.hasOwnProperty(‘getFullYear’)
оценивается как true
.
Таким образом, когда среда выполнения JavaScript не могла найти метод getFullYear
для объекта myBirthday
, она проверяла его прототип Date.prototype
. У этого объекта действительно есть метод getFullYear
, поэтому он его выполнил.
Полное раскрытие информации: в душе я разработчик. Я предпочитаю писать такие вещи, как dog.isWalking, а не «собака идет». Учитывая, что я занимаюсь этим годами, мои навыки английской грамматики медленно, но верно ухудшаются. Когда я работал над этой серией, я заметил, что временами то, что я писал, звучало как тарабарщина. Итак, я опирался на ChatGPT. Я также попытался сгенерировать изображения для этой статьи, используя DALL·E 2; к сожалению, все они были… странными.