Если вы когда-нибудь хотели создать кучу бутербродов на JavaScript, вы попали в нужное место! Я собираюсь провести вас через процесс приготовления бутербродов с использованием псевдоклассического шаблона для конструкторов классов в JavaScript.

Но сначала давайте поговорим о том, что такое классы и почему вы должны заботиться о них. Как оказалось, классы на самом деле не существуют в JavaScript так, как в других объектно-ориентированных языках. Класс JavaScript — это просто синтаксический сахар для создания объектов, которые наследуют свои прототипы (читай: свойства и методы) от других объектов.

Классы чрезвычайно важны для вас как для программиста, потому что они позволяют вам создавать множество объектов с общей функциональностью, не переписывая один и тот же код снова и снова. В этом уроке мы создаем практически бесполезный сэндвич-объект, но вы можете использовать эти знания для создания более сложных и полезных объектов в своих программах. Распознавание этого синтаксиса также окажется полезным при чтении и реализации чужого кода.

Основной сэндвич

Итак, мы решили построить кучу сэндвич-объектов. Каждый сэндвич в нашей группе сэндвич-объектов будет содержать хлеб, сыр и мясо. Конструктор псевдоклассического класса ES5 для бутерброда может выглядеть так:

Мы можем использовать наш конструктор класса Sandwich для создания экземпляра бутерброда, например:

Здесь мы передали аргументы «пшеница», «американец» и «ветчина» в качестве свойств нашего хлеба, сыра и мяса. Мы можем представить себе создание похожих сэндвич-экземпляров, передавая разные аргументы для этих свойств.

Базовый бутерброд с методами

Давайте на мгновение отложим недоверие и предположим, что мы хотим, чтобы наши бутерброды могли говорить. Это звучит как работа для метода, и обычно мы определяем методы в свойстве прототипа конструктора. Прототип — это просто объект (как и все остальное в JavaScript). У каждого конструктора есть объект-прототип, и каждый объект-прототип имеет предустановленное свойство конструктора, которое просто ссылается на сам конструктор. Вы еще не запутались? Давайте посмотрим, как это выглядит, и развеем любую путаницу.

Здесь мы добавили метод talk к объекту-прототипу Sandwich. Если мы вызовем hamSandwich.talk(), то увидим в консоли «Я такой вкусный!». Если мы зарегистрируем hamSandwich.constructor, то увидим [Function: Sandwich], что сообщит нам, что наш объект hamSandwich был создан с использованием конструктора Sandwich. Это интересно, потому что позволяет нам определить, откуда наш hamSandwich наследует свои методы.

Необычный сэндвич

Теперь, когда мы рассмотрели основные конструкторы, давайте оживим ситуацию, создав причудливый класс-сэндвич. Наши причудливые сэндвичи будут наследовать все свойства и методы нашего обычного класса сэндвичей. У них также будет свойство приправы, и они, вероятно, смогут рассказывать анекдоты. Давайте взглянем на код и посмотрим, как мы этого добьемся.

Первая строка в теле нашего конструктора FancySandwich может сбить вас с толку, если вы не знакомы с этим шаблоном построения классов. По сути, мы вызываем конструктор Sandwich в контексте нашего только что созданного экземпляра FancySandwich. Простой способ представить это так: сначала мы делаем обычный Sandwich, а затем добавляем в него дополнительные свойства и методы FancySandwich.

Необычный объект-прототип бутерброда

Первые две строки после нашей функции FancySandwich устанавливают прототип FancySandwich и являются ключом к пониманию того, как псевдоклассические экземпляры наследуют свои методы. Однако эти строки могут сбивать с толку, поэтому давайте рассмотрим их поближе:

В первой строке в качестве объекта-прототипа FancySandwich задается объект, у которого в качестве прототипа используется прототип Sandwich.

Говоря более разумно, эта строка позволяет объекту-прототипу FancySandwich искать и использовать методы, определенные в прототипе Sandwich. Вы, возможно, заметили, что мы никогда не определяли метод talk() в нашем прототипе FancySandwich, но мы все еще можем заставить наш mayoSandwich говорить. Наш FancySandwich использует очень важную концепцию JS, называемую «цепочкой прототипов», чтобы это произошло. Вот как это работает:

  1. Мы вызываем mayoSandwich.talk()
  2. Интерпретатор ищет метод talk() в объекте-прототипе конструктора, создавшего mayoSandwich. Конструктор, создавший mayoSandwich, называется FancySandwich, а объект-прототип для FancySandwich имеет только один метод, а именно, tellJoke().
  3. Интерпретатор обращается к объекту-прототипу, на который ссылается объект-прототип FancySandwich. Этот объект-прототип является объектом-прототипом Sandwich.
  4. Интерпретатор видит метод talk() объекта-прототипа Sandwich и выполняет метод talk() для нашего mayoSandwich.

Вы можете задаться вопросом, почему здесь вообще существует вторая строка, поскольку я только что сказал вам, что каждый конструктор поставляется с объектом-прототипом с предопределенным методом конструктора. Напомним, что первая строка в этом фрагменте полностью перезаписывает встроенный объект-прототип для FancySandwich и заменяет его пустым объектом, который обращается к объекту-прототипу Sandwich. Если мы явно не определим конструктор для объекта-прототипа FancySandwich, он будет обращаться к объекту-прототипу Sandwich для свойства конструктора. Это может ввести нас в заблуждение, думая, что любой экземпляр FancySandwich на самом деле был создан Sandwich. Ужас!

Поздравляем! Теперь мы создали простую функцию-конструктор для Sandwich и более сложный конструктор для FancySandwich, который ссылается на наш Sandwich базового уровня. Вы будете часто сталкиваться с этим шаблоном класса как разработчик, поэтому важно, чтобы вы могли понять и распознать его.