Начинающие разработчики JavaScript часто по ошибке используют одно слово - «прототип» - для обозначения двух разных концепций. Но в чем именно разница между «прототипом объекта» и «свойством прототипа» функций JavaScript?

Мне казалось, что я понял концепцию «прототипов» и прототипного наследования в JavaScript. Но меня продолжали сбивать с толку ссылки на «прототип» в коде и документации.

Большая часть моего замешательства исчезла, когда я понял, что когда я пишу о JavaScript, люди часто случайно используют слово «прототип» для описания двух различных, но связанных концепций.

  1. Прототип объекта: объект шаблона, от которого другой объект JavaScript наследует методы и свойства. (MDN)
  2. Не перечислимое prototype свойство в функциях JavaScript: удобство для облегчения использования шаблона проектирования (этот шаблон проектирования будет подробно описан в ближайшее время!) .
    Сам по себе не имеет смысла, пока не будет намеренно настроен на использование некоторой функции, связанной с наследованием. Наиболее полезно при использовании с функциями конструктора и фабричными функциями (объяснение скоро!). Хотя все функции JS по умолчанию имеют свойство. Содержит свойство constructor, которое относится к исходной функции.

Долгое время меня устраивало определение 1, но не определение 2.

Почему это различие важно?

Прежде чем я понял разницу между «прототипом объекта» и «неперечислимым prototypeproperty в функциях», я обнаружил, что меня смущают такие выражения:

Array.prototype.slice.call([1, 2], 0, 1);
// [ 1 ]

(боковая панель: первый, но не единственный, шаг к пониманию вышеизложенного - это понимание call(). На всякий случай сделаем небольшое напоминание!)

Вопрос, на который я раньше не мог ответить:

  • «Почему мы ищем slice в прототипе Array конструктора? Разве сам конструктор Array не должен содержать slice метод, а его прототип просто должен содержать некоторые действительно низкоуровневые методы, общие для всех объектов? »

Эти вопросы были полностью прояснены, когда я пришел к пониманию шаблона проектирования, для включения которого существует свойство prototype в функциях конструктора Array.

3 шага к пониманию свойства прототипа в функциях JS

Чтобы понять свойствоprototype в функциях JS, вам необходимо понять шаблон проектирования, который он включает. Я построю понимание этого паттерна, сначала проработав две менее предпочтительные альтернативы.

Реализация 1: шаблон функционального класса

Представьте, что мы хотим создать игру, в которой мы взаимодействуем с собаками. Мы хотим быстро создать много собак, у которых есть доступ к общим методам, таким как pet и giveTreat.

Мы могли бы начать реализацию нашей игры, используя Шаблон функционального класса следующим образом:

Давайте немного поправим это, сохранив эти методы в их собственном объекте. Затем расширите их внутри фабричной функции createDog.

Хотя об этой реализации легко рассуждать, и она удобно отражает наследование на основе классов на других языках, у нее есть по крайней мере одна серьезная проблема: мы копируем определения наших методов в каждый объект dog, который мы создаем, используя нашу фабрику functioncreateDog.

Это занимает больше памяти, чем необходимо, и не является СУХИМ. Было бы неплохо, если бы вместо копирования определений методов в zeus и casey мы могли бы определять наши методы в одном месте. Значит, zeus и casey указывают на это место?

Рефакторинг 1: реализация шаблона проектирования «Прототипный класс»

Прототипное наследование дает нам именно то, о чем мы просили выше. Это позволит нам определить наши методы в одном объекте-прототипе. Затем пусть zeus, casey и бесконечно больше подобных им объектов указывают на этот прототип. zeus и casey затем будут иметь доступ ко всем методам и свойствам этого прототипа по ссылке.

ПРИМЕЧАНИЕ. Для менее знакомых есть много отличных руководств, которые объясняют концепцию прототипного наследования гораздо глубже, чем я здесь!

ПРИМЕЧАНИЕ К МОИМ ПРИМЕРАМ НИЖЕ: Для педагогической ясности я использую фабричные функции с именем createDog , а не конструкторские функции ES5, чтобы реализовать прототипную модель наследования. Я предпочитаю использовать фабричные функции, потому что в них меньше магии, творится под капотом и синтаксического сахара, чем в конструкторах ES5. Надеюсь, это поможет сосредоточиться на проблеме!

Большой! Теперь объекты, соответствующие zeus и casey, сами по себе не содержат копий методов giveTreat и pet. Вместо этого объекты ищут эти методы в их prototypemethodsForShowingAffection.

Но было бы неплохо, если бы methodsForShowingAffection был инкапсулирован в createDog фабричную функцию? Это даст понять, что эти методы предназначены для использования только с этой функцией. Итак, простой рефакторинг оставляет нам:

Рефакторинг 2: Prototypal Inheritance + свойство prototype в функциях фабрики

Большой! Но разве не methodsForShowingAffection и длинное и странное название собственности? Почему бы не использовать что-то более общее и предсказуемое? Оказывается, разработчики Javascript предоставляют нам именно то, что мы ищем. Встроенное свойство prototype для каждой функции, включая свойство фабрики functioncreateDog.

Обратите внимание, что в этом свойстве prototype нет ничего особенного. Как показано выше, мы могли бы достичь точно такого же результата, установив прототип createDog на отдельный объект с именем methodsForShowingAffection. Нормальность свойства theprototype для функций в Javascript предполагает предполагаемый вариант использования: удобство, предназначенное для облегчения общего шаблона проектирования. Ни больше ни меньше.

Дополнительная информация:

Дополнительные сведения о свойстве prototype для функций в JavaScript см. В разделе прототип функции в этом сообщении блога Себастьяна Порто.

Статья МДН о прототипах.