JavaScript широко известен тем, что по своей природе является чрезвычайно гибким. В этом посте мы покажем несколько примеров использования этого преимущества при работе с функциями. Поскольку функции можно передавать куда угодно, мы можем поместить их в аргументы функций. Мой первый практический опыт работы с программированием в целом был связан с началом написания кода на JavaScript, и одна из концепций на практике, которая меня смущала, была передача функций в другие функции. Я пытался сделать некоторые из этих "продвинутых" вещей, которые применяют все профессионалы, но в итоге у меня получалось что-то вроде этого: Это было абсолютно нелепо и даже затрудняло понимание того, зачем мы вообще передаем функции в другие в реальном мире, когда мы могли бы просто сделать это и вернуть обратно то же самое: Но почему этого недостаточно для более сложных ситуаций? Какой смысл в создании пользовательской функции getDate(callback) и необходимости делать дополнительную работу, кроме как для ощущения собственного превосходства?   Затем я продолжил задавать дополнительные вопросы об этих вариантах использования и попросил привести способ подходящего применения на форуме сообщества, но никто не захотел объяснить и продемонстрировать это на примере. Оглядываясь назад, я понял, что проблема была в том, что мой разум еще не знал, как мыслить программно. Требуется некоторое время, чтобы перестроить мышление с привычного образа жизни на компьютерное программирование. Поскольку я понимаю разочарование при попытке понять, когда же функции более высокого порядка полезны в JavaScript, то решил написать эту статью, чтобы шаг за шагом объяснить правильный сценарий использования, начиная с очень простой функции, которую может написать каждый, и проделать путь от нее к сложной реализации, обеспечивающей дополнительные преимущества. Сначала мы начнем с функции, которая предназначена для достижения какой-либо цели. Как насчет функции, которая примет объект и вернет новый, который обновит стили так, как мы этого хотели? Давайте поработаем с этим объектом (мы будем называть его компонент): Мы хотим, чтобы наша функция сохраняла height не менее 300 и применяла border к компонентам кнопок (компоненты с type: 'button') и возвращала их нам. Это может выглядеть примерно так: Результат: Предположим, что мы пришли к идее, будто каждый компонент может иметь больше компонентов внутри себя, помещая их в свойство children. Таким образом, необходимо сделать так, чтобы это свойство обрабатывало и внутренние компоненты.  Итак, давайте зададим такой компонент: Очевидно, что наша функция пока не справляется с поставленной задачей: Поскольку мы недавно добавили в концепцию понятие дочерних компонентов, теперь ясно, что для получения конечного результата существует как минимум два различных способа. Настало время подумать об абстракции. Абстрагирование частей кода в многократно используемые функции делает ваш код более читабельным и удобным для сопровождения, поскольку это предотвращает такие неприятные ситуации, как отладка некоторых проблем в деталях реализации чего-либо. Когда мы абстрагируем небольшие части от чего-то, целесообразно задуматься о том, как впоследствии собрать эти части вместе в то, что будет называться композицией. Чтобы понять, от чего абстрагироваться, подумайте, какова наша конечная цель: "Функция, которая будет принимать объект и возвращать новый, в котором стили обновлены таким образом, как мы этого хотим". По сути, вся суть этой функции заключается в преобразовании значения, чтобы оно было в том представлении, которое мы ожидаем. Помните, наша первоначальная функция преобразовывала стили компонента, но затем мы также добавили, с помощью свойства children, что компоненты могут содержать компоненты внутри себя, поэтому мы можем начать с абстрагирования этих двух частей, поскольку существует большая вероятность возникновения ситуаций, когда нам понадобится больше функций, которые будут делать подобные вещи со значением. В рамках данного руководства мы будем называть эти абстрагированные функции резольверами (распознавателями): Результат: Далее поговорим о том, как этот код может вызвать катастрофические ошибки -- ошибки, которые приведут к краху вашего приложения. Если мы внимательно изучим резольверы и посмотрим, как они используются для вычисления конечного результата, то увидим, что они могут легко сломаться и привести к краху нашего приложения по двум причинам: Резольверы мутируют - Что, если произойдет неизвестная ошибка и значение мутирует неправильно, ошибочно присвоив неопределенные величины? Значение также изменяется вне функции, потому что оно было мутировано (поймите, как работают ссылки).  Если мы уберем return component из resolveStyles, то сразу же столкнемся с TypeError, потому что это значение станет входящим для следующей функции резольвера: Резольверы переопределяют предыдущие результаты - Это не очень хорошая практика, которая нарушает цель абстракции. Наш resolveStyles может вычислять свои значения, но это не будет иметь смысла, если функция resolveChildren вернет совершенно новое значение. Мы можем спокойно двигаться к нашей цели, сделав эти функции неизменяемыми и гарантируя, что они всегда возвращают один и тот же результат, если им дано одно и то же значение. Внутри нашей функции resolveStyles мы можем вернуть новое значение (объект), содержащее измененные параметры, которые мы объединим с исходными.Таким образом мы можем гарантировать, что резольверы не переопределяют друг друга, а возврат undefined не повлияет на другой код впоследствии: Если у нас есть 10 резольверов стилей и только 1, работающий с дочерними элементами, это затрудняет обслуживание, поэтому мы можем разделить их в той части, где они будут совмещены: Код, вызывающий эти резольверы, был абстрагирован в отдельную функцию, чтобы мы могли использовать его повторно, а также для уменьшения дублирования. А что если у нас есть резольверы, которым требуется дополнительный контекст для вычисления результата? Например, если у нас есть функция резольвер resolveTimestampInjection, которая вводит свойство time, когда используется некоторый параметр опций, переданный где-то в обертке? Было бы неплохо дать резольверам возможность использовать дополнительный контекст, а не просто получать значение component в качестве аргумента. Мы можем предоставить такую способность с помощью второго параметра наших функций резольвера, но я думаю, что эти параметры следует сохранить для абстракций более низкого уровня для каждого компонента. Что если бы у резольверов была возможность возвращать функцию и вместо этого получать нужный им контекст из аргументов возвращаемой функции? Это может выглядеть примерно так: Хотелось бы включить эту функциональность без изменения поведения исходного кода: Именно здесь начинает проявляться потенциал композиции функций высшего порядка, и хорошая новость заключается в том, что их легко реализовать! Чтобы включить эту функциональность, давайте перейдем на шаг выше в абстракции, обернув резольверы в функцию более высокого порядка, которая отвечает за внедрение контекста в функции резольверов нижнего уровня. Теперь мы можем возвращать функцию из любой функции, которую мы зарегистрируем как резольвер, и при этом поведение нашего приложения останется прежним, примерно так: Прежде чем я покажу последний пример, давайте рассмотрим функцию высшего порядка makeInjectContext и разберем, что она делает: Сначала она принимает объект, который вы хотите передать всем резольвер-функциям, и возвращает функцию, принимающую в качестве аргумента функцию обратного вызова (callback). Этот параметр коллбэка позже станет одной из исходных функций резольвера. Причина, по которой это делается, заключается в том, что мы выполняем так называемую "обертку". Мы обернули коллбэк внешней функцией, чтобы внедрить дополнительную функциональность, сохранив при этом поведение нашей исходной функции, обеспечив вызов коллбэка внутри нее. Если возвращаемый тип результата коллбэка - функция, то можно предположить, что коллбэку нужен контекст, поэтому мы вызываем результат коллбэка еще раз - и именно здесь мы передаем контекст. Когда мы вызываем этот коллбэк (функцию, предоставленную вызывающей стороной) и выполняем некоторые вычисления внутри функции-обертки, у нас есть значения, поступающие от обертки и от вызывающей стороны. Это хороший вариант использования для достижения нашей конечной цели, поскольку нам хотелось объединить результаты вместе, а не предоставлять каждой резольвер-функции возможность переопределять значение или результат предыдущего резольвера! Не стоит забывать, что существуют и другие расширенные сценарии использования для решения различных проблем, и это хороший пример демонстрации конкретной ситуации, когда нам была нужна подходящая стратегия, чтобы ее успешно разрешить. Вы, вероятно, пытались реализовать множество расширенных сценариев использования каждый раз, когда видели для этого открытую возможность - что является плохой практикой, потому что некоторые продвинутые паттерны всегда могут быть лучше других в зависимости от ситуации! И теперь наша функция start нуждается в корректировке для функции более высокого порядка makeInjectContext: И мы по-прежнему получаем обратно объект с ожидаемыми результатами! Заключение На этом я завершаю этот пост! Надеюсь, что вы нашли это полезным и ждите новых материалов в будущем! Материал подготовлен в рамках курса JavaScript Developer. Basic. Всех желающих приглашаем на открытый урок «Как сверстать любой вебсайт: Float, Flexbox, Grid». На открытом уроке мы рассмотрим, как проработать вебдизайн и перенести его на любой интерфейс. Разберем самые популярные способы сверстать макет, поработаем с такими инструментами, как Float, Flexbox, Grid   >> РЕГИСТРАЦИЯ  Редактор