Несколько недель назад, просматривая Medium, я наткнулся на статью под названием Быстрое объяснение объектов JavaScript автора Tamal Anwar. Я без ума от статей и руководств, объясняющих программирование или технологии в понятной для новичков форме, потому что они были Божьим даром, когда я проходил свои первые недели кодирования. Я также из тех людей, которые прочитали достаточно комиксов, чтобы построить из них свою собственную крепость, поэтому статья, посвященная объектам JavaScript с использованием Лиги справедливости в качестве примера, была просто взрывом чтения.

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

"Мы могли бы даже расширить эту статью, объяснив прототипы/расширения классов на примерах помощников и протеже. Это был очень креативный способ объяснения объектов, молодец!»

На что Тамал фактически ответила, фактически поощряя меня идти вперед и делать это:

«Давайте, приятель, напишите один и дайте ссылку на эту статью и дайте мне знать. Я не все знаю об объектах и ​​кроличьей норе JS. Было бы интересно познакомиться с поп-культурой :)”

Моей первой мыслью было: «Хех. Это будет день. Я еще зеленый разработчик, чему я могу научить кого-то? И мой английский все еще терпит неудачу здесь и там. Кто воспримет меня всерьез?».

Второй мыслью было: «Хватит оправдываться. Вы скучаете по писательству и знаете это. СДЕЛАЙТЕ ЭТО».

(которое я мысленно продиктовал голосом Бэтмена и с гифкой Брюса Уиллиса, произносящего цитату «кто-нибудь, вызовите скорую помощь». Потому что на данный момент я практически говорю на мемах как на своем втором языке).

Итак, приступим: это попытка объяснить наследование объектов JavaScript, функции прототипов и классов… с помощью персонажей комиксов!

Отказ от ответственности 1. Для контекста загляните в исходную статью Тамала (ссылка на нее выше), чтобы получить представление о героях/теме кодирования и о том, что я пытаюсь здесь сделать. Кроме того, это интересное чтение.

Отказ от ответственности 2. Я занимаюсь программированием около 6 месяцев. Эта статья предназначена для начинающих, а не для экспертов. Здесь могут быть ошибки - проявите милосердие. Конструктивная критика и советы о том, как улучшить мои примеры, более чем приветствуются!.

В этой статье я собираюсь много рассказать об одном из моих любимых вымышленных персонажей всех времен: Найтвинге. Дик Грейсон, ранее известный как Робин, первый Чудо-мальчик и самый первый помощник комиксов, отказался от мантии Робина и роли протеже Бэтмена в 1984 году, приняв новую личность супергероя Найтвинга. С тех пор Грейсон возглавил «Юных титанов» и даже на какое-то время заменил Брюса Уэйна в роли Бэтмена.

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

И те из вас, кто уже знаком с наследованием, вероятно, поймут, к чему все идет.

При создании объектов Javascript нам иногда нужно создавать объекты, очень похожие по свойствам друг на друга. Теоретически мы могли бы просто написать два объекта по отдельности, например так:

Как видите, объекты не только похожи по своей структуре, но даже обладают некоторыми общими свойствами, например временем работы. У них также может быть свойство или два, которых нет у другого (у Найтвинга нет назначенного свойства «торговая марка_снаряда», а у Бэтмена нет назначенного «оружия»).

Могу ли я просто… не знаю, использовать объект «Бэтмен» как своего рода чертеж, чтобы сделать его копии с нужными мне модификациями? Чтобы я мог сэкономить время на втором объекте, и даже больше, если после этого мне придется включать еще четыре объекта?

Да, вы можете: то есть в очень грубой и лаконичной форме о том, что такое наследование. И есть несколько способов сделать это.

Функция-конструктор

Помните, как я только что написал два объекта, один для Бэтмена и один для Найтвинга? Давайте сделаем это снова, но на этот раз с помощью функции конструктора объекта. Например:

Функция-конструктор принимает ряд параметров и использует метод this, чтобы разрешить новые экземпляры каждого параметра. Если это поможет, подумайте о методе конструктора как о 3D-принтере, подготовленном для получения информации об объекте, который он должен распечатать: высоте, ширине, весе, гладкости поверхности, Особенности…

Теперь, когда я создал функцию-конструктор Vigilante, я буду использовать ее для создания Batman и Nightwing в одной строке для каждого из них:

Преимущества конструктора могут быть не столь очевидны, если мы только что построили два объекта. Но так как моя функция Vigilante все еще включена, я могу использовать ее для создания большего количества объектов.Столько, сколько захочу!

Представьте, сколько строк кода мне пришлось бы написать, если бы я построил каждый Vigilante как отдельный объект. Я сэкономил себе время и работу! Теперь я могу побаловать себя чем-нибудь…

… Подожди. Это не совсем то, что я хотел! В начале у Найтвинга были дубинки, а у Бэтмена был его культовый капюшон с остроконечными ушами и его угрожающий плащ, и теперь они их потеряли! И хотя Найтвинг все еще может надрать задницу без своих дубинок, смотреть, как Бэтмен прыгает с крыши на крышу без плаща и капюшона, было бы… скорее сбивающим с толку, чем потрясающим. Мне нужно исправить это. В Бэтпещеру! Я имею в виду… вернуться к моему коду!

Простое решение — вернуться к моей функции конструктора объектов и включить параметр «оружие», чтобы я мог дать Найтвингу его дубинки, Красному Капюшону — два пистолета, а Зеленой Стреле — лук и стрелы. Но это противоречило бы Бэтмену и Бэтгерл, поскольку они не используют оружие (Бэтаранги — это ГАДЖЕТЫ. Позор вам, если вы думаете, что это оружие!).

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

Теперь, если я запущу console.log(Nightwing), я получу назначенную ему итерацию Vigilante… но с параметром «оружие» после имени героя, в частности, с его дубинками. То же самое произойдет, если я запущу console.log(Batman): я увижу эту итерацию Vigilante с его плащом и капюшоном в конце.

Хороший! Теперь давайте посмотрим на пример того, как мы можем использовать наследование для чего-то большего, чем создание объектов. Пусть наши герои что-нибудь сделают, ладно?

Использование наследования: прототипы, классы и другие функции

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

Помните, как ранее я сравнивал функцию конструктора с 3D-принтером, который распечатывает копии из набора данных, которые он в нем сохранил? Очень похожая логика может быть использована для объяснения функции прототипа. В конце концов, слово «прототип» можно примерно определить как «исходная модель чего-то, на которой будут основаны будущие копии того же самого «чего-то»», и это в значительной степени то, что прототип Функция делает: она вызывает свою исходную модель для выполнения операции с некоторыми из своих копий. Структура будет выглядеть так:

[Имя конструктора].прототип.[Имя функции] = function() {

// Код для выполнения

}

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

Если мы запустим, например, Batgirl.introduce(); учитывая итерацию Vigilante для Batgirl, которую я написал ранее,функция возвращает:

"Бэтгёрл — участница The Birds of Prey и отличный хакер"

и мы получим аналогичные результаты для каждой из наших переменных Vigilante в соответствии с их конкретным именем героя, информацией о команде и навыках.

Эти примеры неплохие… Но, может быть, несколько простенькие. Я уверен, что мы можем использовать наследование более интересным образом.

Ага! Что такое герой без угроз, которые нужно преодолеть, и врагов?

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

Итак, я написал небольшую функцию, которая, учитывая мой конструктор объекта Vigilante, объявляет, что Deathstroke собирается атаковать определенный город. В зависимости от города, который функция получила в качестве параметра, моя функция вернет имя героя, который выдержит вызов и защитит город. Бэтмен, Найтвинг и Зеленая стрела в какой-то момент сражались с Дезстроуком, поэтому в этом примере я буду использовать их троих.

Если я запускаю DeathstrokeAttacks(‘Gotham); вот что я получаю взамен:

И я получу аналогичные результаты для Зеленой Стрелы и Найтвинга, если запущу функцию DeathstrokeAttacks() с соответствующими городами в качестве параметра.

Хороший! Теперь у всего сообщества героев есть система, определяющая, кто должен защищать каждый город в случае нападения злодеев! Ну, за исключением случаев, когда Корпус Синестро атакует, потому что это находится в юрисдикции Зеленого Ланта… Упс! Я на секунду сорвался с рельсов. Давайте вернемся к коду…

Классы и расширения классов

И последнее, но не менее важное: я быстро пройдусь по объявлению class, текущему стандарту ES6 для всей этой наследственной деятельности. Теперь, наиболее проницательные из вас, вероятно, уже подумали «подождите, этот парень забыл несколько вещей об этих собственных примерах по пути. Разве он не упомянул, что если два объекта имеют одно и то же свойство, с этим можно что-то сделать? Разве он не начал с упоминания об использовании Бэтмена в качестве чертежа для других героев?»

Ну, так же, как это якобы делает Бэтмен, я планировал на пару шагов вперед.

В Javascript класс является более сильным и универсальным братом прототипа. Базовая структура такая же, но мы можем не только прикрепить несколько методов к классу, мы также можем использовать изящное ключевое слово super для прямого вызова родительских элементов без необходимости их повторного объявления с помощью '. это'.

Как и многие другие герои, Бэтмен вдохновил некоторых других стать героями или линчевателями. Независимо от того, были ли они непосредственно обучены и воспитаны им или просто контролировались, список значительно вырос, и теперь фанаты обычно называют его «Семьей летучих мышей» (как и ваш покорный слуга). Итак, давайте снова объявим Бэтмена, но на этот раз как экземпляр класса Hero.

Batman.defend(); выведет на консоль "Бэтмен защищает Готэм".

… Ну, пока что не кажется, что есть преимущество перед методом прототипа, верно?

Не для исходного класса. Но мы начинаем получать преимущества, когда расширяем наш исходный класс.

Подожди! Что только что произошло? Почему там "супер"? Как мы включили this.weapons в класс Sidekick? И почему я включил функцию Defence() внутри родительского класса Hero?

Давайте рассмотрим это медленно.

В предыдущих случаях со стандартными функциями-конструкторами мы включали наши свойства в скобки функции. Затем мы бы объявили копии конструктора с «новой» итерацией нашего конструктора. Если мы хотим включить методы, нам придется сделать это с помощью Object.prototype.function.

Классы дают нам дополнительную гибкость. В этом случае мы объявляем свойства нашего исходного классавнутри конструктора (в данном случае личность, город, команда, навыки и имя героя). Но с классами мы можем объявить дополнительные методы внутри конструктора класса, поэтому функция Defence() включена в класс Hero. Таким образом, не только каждый экземпляр класса Hero сможет использовать этот метод без необходимости объявлять дополнительный прототип вне класса, но и каждый расширенный от него класс тоже сможет это сделать.

Вместо определения помощника как совершенно нового класса, вы видите, что я объявил его как класс, который расширяет класс героя. С точки зрения непрофессионала это означает, что класс Sidekick сможет заимствовать свойства или методы, объявленные в его родительском классе.

Мои помощники смогут использовать этот метод, без необходимости писать его. Удобно, не так ли? Если я напишу Nightwing.defend();, консоль напечатает «Nightwing защищает Блюдхейвен», как это было с Бэтменом.

С расширением предыдущего класса вместо того, чтобы объявлять тот же набор свойств с нуля, я могу перечислить их внутри «супер» конструктора. Если свойство существует внутри своего родительского класса, мой расширенный класс Sidekick заимствует его и реализует напрямую. Обратите внимание, что для класса Sidekick мне не нужно было объявлять this.identity, this.city … Все сначала. Мой «супер» конструктор позаботился об этом за меня.

Еще одним преимуществом расширений класса является то, что расширенные классы могут реализовывать новые свойства внутри своего конструктора после заимствования того, что им нужно, от его родителя. Помните, как раньше, когда я заметил, что Найтвинг потерял свои верные дубинки, мне пришлось включить их после того, как Найтвинг был объявлен «новым мстителем»? С помощью класса я могу включить это новое свойство после остальных свойств, заимствованных суперконструктором. Снова проверьте расширенный класс Sidekick, немного выше этого абзаца, чтобы увидеть, как я это сделал (и это также избавляет меня от необходимости снова публиковать одно и то же изображение).

С помощью этого трюка я могу безопасно включить «дубинки эскримы» после остальных свойств, перечисленных в конструкторе, и если я зайду в console.log(Nightwing), я увижу свойство его оружия в списке после остальных. Этот механизм построения позволяет нам заимствовать то, что нам нужно, из исходного класса, позволяя нам добавлять свойства в расширенный класс по своему усмотрению.

Финальные трюки или своего рода попытка…

В качестве заключительного замечания…. Давайте вспомним, как в самом начале Бэтмену было назначено свойство «торговая марка_gear» для его культового изображения, и я подумал «Я не хочу назначать это свойство Найтвингу». Можем ли мы на самом деле это сделать? Можем ли мы удалить свойство из родительского конструктора, чтобы оно не передавалось его расширенным классам?

… Для этого может быть пуленепробиваемый способ. К сожалению, как, наверное, и каждый программист и веб-разработчик, я все еще учусь. Stack Overflow не дал мне многого для этого, но после того, как я покопался в Repl.it, я нашел вариант, который поможет на сегодня.

Вот что я сделал:

Оператор delete позволяет нам удалять свойства объекта, если они не заданы как переменные или константы. Но использовать «удалить» здесь было сложно, потому что если бы я попытался удалить свойство, которое конструктор изначально имел в своей третьей позиции, при попытке доступа к моему новому объекту Sidekick, какой бы ни была третья позиция в моем объекте Sidekick, это третье свойство вернет «неопределенное».

Я мог бы обойти это, вернувшись к родительскому классу и поместив свойство, предназначенное для удаления, в последнюю позицию (это противоречило моему первоначальному намерению не изменять указанный родительский класс, ну да ладно…).

После этого, если я напишу и выполню console.log(Batman) и console.log(Nightwing), вот что я получу:

Герой {
личность: «Брюс Уэйн»,
город: «Готэм»,
команда: «Лига справедливости»,
навыки: «детектив»,
> heroName: 'Бэтмен',
товарный знак_gear: 'плащ и капюшон'

Sidekick {
имя: 'Дик Грейсон',
город: 'Блюдхейвен',
команда: 'Юные Титаны',
навыки: 'акробат',
> heroName: 'Nightwing',
оружие: 'эскримские дубинки' }

Буйя!… Вроде того. Я достиг своих первоначальных целей: реализовать только те свойства, которые мне нужны, исключить те, которые мне не нужны в моих расширенных классах, и иметь возможность включать новые свойства для моих расширенных классов. Но я все еще чувствую, что обманул свой выход из этого, и, вероятно, есть лучший метод…

Ну что ж!

Это первая статья, которую я написал за ДОЛГОЕ время (я был журналистом, прежде чем стать разработчиком, на следующий день), и у меня был абсолютный взрыв, написание статьи и функций для тестирования и иллюстрации моих случаев. Я хотел бы еще раз поблагодарить Тамала Анвара за то, что он дал мне идею для этой статьи, и моего друга и коллегу Мигеля Колладо за корректуру кода.

И, конечно же, большое СПАСИБО всем, кто смог преодолеть эту стену текста. Если вам понравилось читать эту статью, хотя бы немного из того, что мне понравилось ее писать, это время того стоило — и даже немного.