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

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

Оглавление:

  1. Что такое функция?
  2. Как определить функцию?
    2.1 Объявление функции
    2.1.1 Вызов функции
    2.1.2 Параметр функции
    2.1.3 Аргумент функции
    2.1.3.1 Объект аргументов
    2.1.3.2 Остальный параметр
    2.1.3.3 Значение параметра по умолчанию
    2.1.4 Оператор возврата
    2.2 Функциональное выражение
    2.2.1 Анонимная функция против выражения именованной функции
    2.2.1.1 Выражение функции против выражения функции
    2.2.2 Сохранение выражения функции в переменной
    2.2. 2.1 Причины сохранения безымянных функций в переменной
    2.2.3 Когда использовать анонимные функции
    2.2.3.1 Функции высшего порядка (HOF)
    2.2.3.2 Замыкания »
    2.2.3.2.1 Замыкания в циклах
    2.2.4 Недостатки анонимных функций
  3. Прямое и косвенное выполнение функций
  4. Асинхронный JavaScript
    4.1 Обратные вызовы
  5. IIFE (Immediately Invoked Function Expression)
    5.1 Зачем нам нужен IIFE?
  6. Методы
    6.1 Встроенные методы
  7. Функции конструктора
  8. Выражения стрелочных функций
    8.1 Минусы стрелочных функций
  9. Функция-генератор
    9.1 Методы-генераторы
    9.2 За и против
    9.3 Использование генераторов
  10. Унарные функции
    10.1 Функции каррирования
    10.1.1 Когда использовать каррирование?
  11. Чистые функции
  12. Функции верхнего уровня
  13. Вложенные функции
  14. Рекурсия
    14.1 Когда использовать рекурсию?
  15. "Заключение"

Что такое функция?

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

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

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

Если я захочу удалить что-то из корзины, я напишу функцию для этого.

Я даже могу написать одну функцию, которая может запускать другую функцию. Я могу управлять функциями с помощью других функций.

Как определить функцию?

Чтобы начать использовать функции, вам необходимо их определить (создать). Вы можете определять функции различными способами.

Распространенные способы определения функции:

  • Объявление функции
  • Выражение функции
  • IIFE (выражение функции с немедленным вызовом)
  • Выражение стрелочной функции
  • Конструктор функций
  • Функция генератора
  • Методы

Мы собираемся рассмотреть каждую из этих тем более подробно и раскрыть множество интересных особенностей функций JavaScript.

Объявление функции

Одним из способов определения функции является объявление функции. Когда вы создаете функцию с именем, она называется объявлением функции (также известное как определение функции или оператор функции). Это самый стандартный, базовый способ написания функции, который вы, будучи новичком, будете очень часто использовать.

Вот иллюстрация объявления функции:

Приведенное выше объявление функции состоит из:

  • Ключевое слово функции
  • Имя этой функции
  • Скобка, содержащая параметр(ы)
  • Тело функции, заключенное в фигурные скобки, с инструкциями, определяющими функцию.

Функция с именем sendMessage получает параметр сообщения и затем записывает это сообщение на консоль. Когда эта функция будет или как эта функция будет это делать? Мы расскажем об этом очень скоро.

  • Ключевое слово функции. Для создания и использования функций всегда необходимо использовать ключевое слово function, поскольку оно является частью синтаксиса JavaScript, помогающим JavaScript идентифицировать то, что вы пытаетесь создать.
  • Имя функции. Имя функции – это способ ее идентификации. Когда у вас есть различные функции, вам нужно различать, какая из них что делает, для дальнейшего использования.
  • Параметр функции: это имя потенциального значения, которое мы можем передать функции. Представьте себе, что у вас есть функция, которая записывает в консоль имя каждый раз, когда вы нажимаете кнопку. Я могу передать внутри функции параметр, который позже будет иметь значение. Эта функция получит это значение и затем запишет его в консоль. Параметры функции не являются обязательными.
  • Тело функции: относится к блоку кода внутри фигурных скобок ({}). Это место, где мы можем предоставить функциональные инструкции.
  • Оператор функции. Внутри тела мы можем иметь функциональные операторы и выражения, которые представляют собой задачи или процессы, которые, как мы ожидаем, будет выполнять функция.

Как вызвать функцию?

После создания функции вам также необходимо ее вызвать. Когда вы вызываете функцию, она выполняет задачи, которые вы записываете в теле функции. Есть функции, которые не нужно вызывать.

Функции также можно вызывать по-разному, но это особая функция, о которой мы поговорим позже.

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

В качестве иллюстрации вот функция hello:

Эта функция сейчас ничего не покажет нам в консоли, потому что мы ее не вызывали.

Поэтому нам нужно добавить еще одну строку:

И только сейчас мы увидим «Чем я могу вам помочь?» в консоли.

Что такое параметр функции?

Параметр функции — это имя, которое мы указываем при создании функции. Это имя потенциально будет значением, которое функция получит и позже выполнит с ним некоторые операции.

Имя, которое вы придумаете, может быть любым, какое оно будет — ваше дело и на результат это не повлияет. Имена параметров необходимы, чтобы связать их с будущими значениями, которые мы планируем передать в функцию.

При выборе параметров следует помнить несколько правил:

  • Имена не могут повторяться. Если вы используете одно имя, следующее должно быть другим.
  • Не всегда нужно использовать параметры. Они не требуются.
  • Порядок имеет значение. Если вы хотите передать параметры имени и фамилии, значения, которые вы планируете передавать, должны соответствовать порядку. Если ваш второй параметр называется «фамилия», но вы передаете имя в качестве второго значения, значением параметра «фамилия» будет значение имени.
  • Вы не определяете тип данных возможного значения. JavaScript — это динамически типизированный язык, что означает, что вам не нужно заранее писать, какой тип данных будет у параметра.

Если вы еще этого не сделали, обязательно прочтите о типах данных в JavaScript.

  • Имена параметров должны быть описательными, рекомендуется использовать верблюжий регистр (lastName, firstName), но это не обязательно.

Наряду с параметрами необходимо знать аргументы.

Что такое аргумент функции?

Аргумент функции — это значение, которое мы передаем функции. Значение будет представлено параметром функции. Когда я использую такие параметры, как имя и фамилия, значения, которые я передаю, будут называться аргументами.

Вот некоторая визуализация:

Согласно изображению выше, имя msg — это параметр, но фактическое значение: «Привет! :)» — это аргумент.

Объект аргументов

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

Например, если у вас есть 4 параметра в функции, но вы передаете 5 аргументов (значений), объект аргументов прочитает все переданные вами аргументы, даже если нет параметра для его чтения.

Вы можете использовать объект аргумента и комбинировать его с функциональностью индекса массива.

Индекс — это позиция в массиве. Индекс самого первого элемента массива равен 0, поэтому его позиция (индекс) равна 0. Следующий элемент имеет индекс 1 и так далее.

Вот пример:

В этой функции я использовал только один параметр, но передал два аргумента. С помощью объекта аргументов я нацелил аргумент на позицию 0, затем на позицию 1 (вторую). Наконец, я нацелился на аргумент с именем параметра.

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

Остальный параметр

Вместо использования объекта аргументов мы также можем использовать параметр rest, который рассматривает один параметр как массив из нескольких аргументов.

Например, если я написал одно имя параметра, но передал несколько аргументов, я фактически могу получить все эти аргументы, «расширив» свой параметр. Чтобы «расширить» имя параметра, вы можете использовать три точки (…) перед именем параметра. Имя может быть любым, поскольку оно создано вами, все остальные предыдущие правила не меняются.

Вот пример, основанный на предыдущей функции:

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

Вот еще один пример, если я передаю один аргумент:

Результат остается аналогичным.

Давайте посмотрим, что произойдет, если я не передам никаких аргументов:

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

Значение параметра по умолчанию

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

Во-первых, давайте вспомним, что происходит, когда мы передаем оба:

Далее давайте посмотрим, что произойдет, если мы этого не сделаем:

Вместо фамилии мы видим неопределенное, потому что параметру не было присвоено никакого значения.

Когда у вас есть реальный сайт и пользователи, может быть не очень хорошо, если пользователь вашего сайта увидит что-то подобное, не так ли?

Как это решить? Мы используем значение по умолчанию!

Как следует из названия, значение по умолчанию — это значение, которое мы можем присвоить параметру, который будет использоваться по умолчанию. Если мы ничего не передадим, начальным значением будет значение по умолчанию.

Какую ценность мы можем дать? Любое значение, которое вам нравится!

В этом случае мы можем добавить значение по умолчанию «Анонимный», которое будет использоваться в случае, если у нас нет фамилии:

Если функция не получает значение фамилии, будет использоваться значение по умолчанию Anonymous.

С другой стороны, есть и другие способы обойти эту проблему.

Вы можете проверить, является ли фамилия неопределенной, и условно показывать разные результаты, но я думаю, что значение по умолчанию более удобно.

Значением по умолчанию может быть любой параметр в любой позиции и даже несколько из них.

Оператор возврата в функциях

В теле функции мы часто используем оператор return. Оператор return возвращает одно конкретное значение и останавливает выполнение функции. Любой код, который у вас есть после оператора return, не будет выполнен. Вот почему оператор return всегда должен быть последним.

Возвращаемое значение может быть любым типом данных. Также не обязательно писать значение после оператора return. Если вы пропустите значение, функция вернет неопределенное значение.

Значение, которое вы хотите вернуть, всегда должно начинаться с той же строки, где находится оператор возврата. Если сделать иначе, этот разрыв строки будет автоматически рассматриваться как точка с запятой. JavaScript автоматически вставляет точку с запятой после оператора return, даже если вы не делаете этого самостоятельно, и эта новая строка будет закрыта.

Чтобы избежать этой проблемы, но в любом случае использовать новую строку, вы можете просто заключить в круглые скобки значение, которое хотите вернуть:

Теперь он должен снова работать правильно.

Что делает оператор return:

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

Если вы не понимаете некоторые термины, не волнуйтесь, мы рассмотрим их очень скоро.

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

Функциональное выражение

Другой способ определения функции — это функциональное выражение.

В функциональных выражениях у вас есть два варианта:

  • Вы можете создать функциональное выражение без имени (так называемая анонимная функция) или
  • Вы можете создать именованное функциональное выражение

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

Анонимные функции (безымянные функциональные выражения) и именованные функциональные выражения

Как следует из названия, анонимная функция — это функция, у которой нет имени, вот пример:

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

В то же время вы можете при желании использовать имя для этой функции. НО в данном случае это будет именованное функциональное выражение, а не анонимная функция. Анонимность означает, что у нее нет имени, она анонимна.

Давайте попробуем это:

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

Мы можем взять объявление функции, созданное ранее, и преобразовать его в анонимную функцию:

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

Однако функция, написанная таким образом, выдаст ошибку:

🚨 Для операторов функций требуется имя функции

Этот код будет рассматриваться как оператор функции, поэтому ему требуется имя.

Функциональное выражение и оператор функции

Оператор функции и выражение функции — это несколько разные вещи в JavaScript.

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

Что такое функциональное выражение?

Выражение в JavaScipt — это блок кода, в котором вы задаете вопрос таким образом, чтобы получить определенное значение.

Допустим, вы производите расчет и хотите проверить, сколько будет 1 плюс 1. Возвращаемое значение будет суммой 1 и 1. И это 1 плюс 1 является выражением. Выражения обычно возвращают определенные значения. А очень похожие утверждения порождают конкретные действия. Например, объявление переменной или выполнение чего-либо с помощью оператора if. Итак, вы делаете своего рода заявление.

В JavaScript вы можете использовать выражения, когда ожидается оператор, но вы не можете использовать оператор, когда ожидается выражение.

Чтобы функция была функциональным выражением, вы не можете начинать ее с ключевого слова function и не использовать имя, если вы ее не вызываете. В таком случае вам нужно его где-нибудь сохранить. Функцию ключевого слова можно трактовать по-разному в зависимости от того, где и как она используется.

Чтобы функция ключевого слова начинала выражение (что-то, что создает значение), ее необходимо использовать в контексте (месте в коде), где JavaScript не ожидает некоторых операторов (команд, действий). В противном случае функция ключевого слова запустит оператор, и нам нужно как-то указать, что нам нужно выражение.

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

  • Сохранение функционального выражения в переменной
  • Или используйте функцию как IIFE (выражение немедленно вызываемой функции).

Сохранение выражения функции в переменной

Давайте исправим ошибку выше, сохранив ее в переменной:

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

Почему?

Этот тип поведения называется выводом имени, который помогает найти функцию с помощью переменных, в которых мы ее сохранили. Итак, я сохранил анонимную функцию (функцию без имени в переменной), а JavaScript все равно дал ей имя, а именно имя переменной, в которой я ее сохранил.

Практические причины сохранять безымянные функции в переменной (помимо технических причин)

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

Чтобы функция работала и выдавала результат, вам нужно вызвать ее по имени. Именование функции помогает ее идентифицировать, поэтому в программировании имя еще называют идентификатором. Точно так же, как и с людьми: когда их называют по имени, они знают, что это их зовут. Представьте, что вы написали 10 различных функций, которые делают совершенно разные вещи. Как заставить эту конкретную функцию что-то делать, если у нее нет имени? Если бы мы решили не называть функциональное выражение и опустить его, нам пришлось бы сохранить его в переменной, чтобы мы могли обратиться к нему позже.

С другой стороны, если вы на 100% уверены, что вам никогда не понадобится повторно использовать эту функцию, у вас есть другие варианты пропустить имя. Это не означает, что функциям всегда нужно имя.

Как многие говорят, «имена — это хорошо», поэтому давайте попробуем понять больше преимуществ именованных функций.

  • Читаемость. Правильное присвоение имени функции помогает сделать код более читабельным, поскольку имена обычно являются своего рода кратким объяснением того, что делает функция. Когда вы работаете над проектом, в котором много функций и над ним работают разные люди, гораздо проще и яснее понять, что делает функция.

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

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

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

  • Рекурсия: рекурсия, которую мы очень скоро рассмотрим более подробно, представляет собой метод, при котором функция может вызывать сама себя. Он выполняет код, а затем снова вызывает себя, который можно остановить, если он соответствует определенным условиям. А чтобы вызвать функцию, ей нужно иметь имя, идентификатор.

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

Когда использовать анонимные функции?

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

Ситуации, когда вы можете использовать анонимные функции:

  • Функции высшего порядка (HOF)
  • Замыкания

Функции высшего порядка (HOF)

Функции высшего порядка — это функции, которые в качестве аргументов могут получать функцию и манипулировать ею или даже возвращать функцию в результате.

Функции в JavaScript рассматриваются на том же уровне, что и типы данных, такие как числа или строки.

Функции рассматриваются как значения, и это делает их более мощными.

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

Функция является функцией первого класса, если она является гражданином первого класса. Редко, но иногда ее также упоминают как функцию первого порядка.

Что делает функции ценностью?

Вы можете назначать функции переменным и использовать их повторно

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

Передайте их в качестве аргументов

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

В приведенном выше примере у меня есть функция Calculate с тремя параметрами. Третий параметр — это обратный вызов, который будет функцией.

У меня есть две различные функции, которые я потенциально могу передать в качестве аргумента (значения). Это может быть либо функция суммы, либо множественная функция. Эти функции сами по себе ничего не делают, пока я не передам их.

Внутри функции расчета я создаю переменную с именем result, и там будет сохранен результат функции обратного вызова.

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

Функция суммы получит значения a и b от функции вычисления и вернет мне сумму.

То же самое относится и к другой функции, множественной.

Храните их в структурах данных, таких как массивы

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

Возврат функций из функций

Функция может не только использовать другие функции, но также возвращать функции. Обычно это происходит, когда речь идет о замыканиях или фабричных функциях, которые мы вскоре рассмотрим.

Вот простой пример:

#1 Сначала я создаю функцию многократного использования, которая получает сообщение и возвращает новую функцию, которая записывает сообщение вместе с его аргументом.

#2 Далее я сохраняю результат функции createMessage в переменных полученного сообщения. Если вы зарегистрируете эту переменную в консоли, она будет ссылкой на функцию. Попробуйте сами!

#3 Далее я вызываю эту переменную, как если бы это была функция, потому что она имеет функциональное значение, и я передаю аргумент «Нина».

Создавайте их динамически

Наконец, вы также можете создавать функции динамически, поскольку они так же динамичны, как и другие значения. Чтобы создать функцию динамически, вы можете использовать конструктор Function. Конструктор функции — это встроенный объект в JavaScript, который может создавать функцию из кода, представленного в виде строки.

Чтобы создать функцию с помощью конструктора функции:

  • Сохраните его в переменной
  • Используйте ключевое слово «new» вместе с конструктором функции.
  • Передайте любое количество параметров
  • В самом конце напишите тело функции

Все должно быть записано в строке.

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

Больше примеров функций высшего порядка

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

Примеры функций высшего порядка:

  • карта()
  • фильтр()
  • Сортировать()

карта()

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

Как видите, я сопоставил каждый элемент и выполнил действие над каждым из них.

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

фильтр()

Фильтр делает что-то похожее на карту. Он выполнит проверку каждого элемента и вернет только те элементы, которые прошли эту проверку.

сортировка()

Сортировка, как следует из названия, используется для сортировки элементов массива. Его можно использовать по-разному в зависимости от того, хотите ли вы сортировать по убыванию или по возрастанию.

В этом примере мы сортируем массив чисел в порядке возрастания.

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

Как видите, каждая функция обратного вызова, которую я использовал, была анонимной функцией. Есть много других подобных примеров, таких как уменьшить(), forEach(), Every() и так далее.

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

Если вы заблудились, напомню, почему мы начали обсуждать эту тему.

Ситуации, когда вы можете использовать анонимные функции:

  • Функции высшего порядка (HOF)
  • Замыкания

Замыкания

Всякий раз, когда мы создаем функцию внутри другой функции, внутренняя функция получает доступ к области видимости внешней функции.

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

Если вы не знакомы с областью действия в JavaScript, обязательно прочитайте мои сообщения о контексте выполнения и объявлениях переменных в JavaScript, чтобы лучше понять замыкания.

В сообщении функции я создал имя переменной. Переменная name будет принадлежать области обмена сообщениями (она доступна только внутри тела сообщения). Затем я создаю еще одну функцию под названием sendMessage, которая имеет собственную локальную область действия.

Однако я в любом случае могу получить доступ к переменной имени из обмена сообщениями родительской функции.

Это называется лексическая область видимости.

Это способность вложенной функции получать доступ к области родительской области. Но родительские функции не будут иметь доступа к области действия вложенных в них функций.

Лексическая область видимости относится к контексту (месту в коде), в котором была объявлена ​​переменная или функция.

Лексическая область видимости и замыкание могут показаться очень похожими и даже одинаковыми.

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

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

Что вы подразумеваете под завершением выполнения внешней функции?

Причина, по которой внешняя функция завершает выполнение первой, заключается в том, как работает JavaScript.

Я определенно рекомендую вам прочитать мой пост о контексте выполнения, потому что он ответит на ваши вопросы.

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

Взгляните на этот пример:

#1 Чтобы движок браузера мог читать JavaScript, он создает среду, называемую контекстом выполнения, где код JavaScript может транслироваться и выполняться в машинном коде.

#2 У нас есть два типа контекстов выполнения — глобальный контекст выполнения и контекст выполнения функции.

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

#3 Когда код читается, первое, что движок хочет найти, — это такие переменные, как const, let и var. Но сначала он будет нацелен на переменные, расположенные вне функции (доступно глобально).

Если вы продолжите читать код построчно, вы увидите, что первая найденная переменная — closureFunc. Когда он найден, он сохраняется в глобальном контексте выполнения, но имя сохраняется, а значение пока не имеет значения. Он сохраняется где-то в памяти для дальнейшего использования. Это имя будет использоваться для обозначения его значения позже.

#4 Когда в памяти нечего сохранять, двигатель вернется к старту. Далее следует обратить внимание на объявление функции. Объявление функции также будет сохранено, как если бы оно было переменной, и будет создано в другом отдельном контексте выполнения функции. Помните, почему функции являются гражданами первого класса? Если нет, то я рекомендую вам вернуться и перечитать о HOF.

#5 Все контексты выполнения накладываются друг на друга.

Первый — это глобальный контекст выполнения, затем над ним находится контекст выполнения функции.

#6 Все, что нужно было сохранить, сейчас ожидает в памяти и переходит в фазу выполнения.

На этапе выполнения значения присваиваются.

Далее по нашему коду мы пытаемся вызвать внешнюю функцию и сохранить результат внутри переменной closureFunc. Это единственная переменная, которую мы создали.

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

Поэтому, как только значение будет присвоено, JavaScript будет знать, что делать дальше.

#7 Внешняя функция выполнится впервые и на этом ее выполнение закончится.

Пока не смотрите на последнюю строку кода closureFunc(). Вам нужно посмотреть на строку, когда мы назначаем внешнюю функцию. Здесь внешняя функция вызывается впервые.

Внутри внешней функции мы объявили переменную InnerVariable и создали функцию Internal. Другими словами, мы не вызывали внутреннюю функцию, мы возвращали внутреннюю функцию.

Это значит, что мы еще не вызвали внутреннюю функцию, а внешняя уже завершила выполнение, она вернула внутреннюю функцию.

#8. Когда внешняя функция возвращает внутреннюю функцию, эта внутренняя функция сохраняется внутри глобальной переменной контекста выполнения — closureFunc.

#9 Наконец, мы вызвали closureFunc, который содержит внутреннюю функцию, которую мы никогда не вызывали.

Теперь, когда мы вызываем closureFunc, будет вызываться внутренняя функция, и это приведет к созданию другого контекста выполнения функции, который помещается в стек вызовов.

В результате он запишет на консоль внешнюю переменную функции, и ее контекст выполнения функции будет извлечен из стека вызовов.

Вот где в игру вступает закрытие.

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

Надеюсь, теперь это имеет немного больше смысла.

Замыкания в циклах

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

Давайте разберемся, как работают замыкания, когда дело доходит до циклов:

Эта функция собирается в журнал консоли «Это» и текущий номер счетчика, который остановится после 5. Что именно здесь происходит?

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

Когда мы вызываем функцию с именем countIt в первый раз, мы также сохраняем ее в переменнойcrementbyOne. Дальше у нас есть. Цикл For, в котором мы начинаем с 0 и заканчиваем цикл, когда он достигает 5. Он продолжает выполнять функцию приращения, и эта встроенная функция удерживает значение счетчика, даже если функция countIt уже завершила выполнение. Мы продолжаем цикл, и переменные счетчика по-прежнему фиксируются внутри функции приращения.

Недостатки анонимных функций

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

Трудно отладить

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

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

Производительность

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

Я знаю, нам приходится время от времени перескакивать с одной темы на другую, потому что многие понятия тесно связаны друг с другом. Напомню, на чем мы остановились. Чтобы функция ключевого слова начинала выражение (что-то, что создает значение), ее необходимо использовать в контексте (месте в коде), где JavaScript не ожидает некоторых операторов (команд, действий). В противном случае функция ключевого слова запустит оператор, и нам нужно убедиться, что мы пишем функцию правильным способом, чтобы получить функциональное выражение.

Для достижения этой цели нам необходимо:

  • Сохранение функционального выражения в переменной
  • Или используйте функцию как IIFE (выражение немедленно вызываемой функции).

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

Далее мы обсудили ситуацию, когда мы можем позволить функции оставаться анонимной.

Ситуации, когда вы можете использовать анонимные функции:

  • Функции высшего порядка (HOF)
  • Замыкания

Мы рассмотрели как функции высшего порядка, так и замыкания.

Теперь мы собираемся перейти к обратным вызовам.

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

Прямое и косвенное выполнение функции

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

Прямой

Прямое выполнение относится к синхронному выполнению кода. Когда мы вызываем одну функцию, затем другую, а затем еще, они не выполняются все сразу. Они следуют друг за другом синхронно.

Быстрый пример:

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

Косвенный

Когда дело доходит до непрямого выполнения, мы связываем его с асинхронным выполнением.

Функции способны выходить за пределы последовательности и не зависеть от порядка выполнения.

Представьте себе ресторан, в котором у нас есть разные функции. Вы заказываете еду, официант принимает заказ, повар готовит еду, другие люди заказывают еду — все это разные функции.

Представьте себе, если бы все эти функции должны были выполняться синхронно.

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

Теперь перейдем к онлайн-ресторану, где вы заказываете еду.

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

Заказы также имеют различные этапы: приняты, статус подготовки, затем отправлены и так далее.

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

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

Все это происходит благодаря асинхронному JavaScript.

Асинхронный JavaScript

Для реализации асинхронного потока существует несколько способов:

  • Обратные вызовы
  • Обещания
  • Асинхронный/ожидание
  • Таймеры
  • Циклы событий
  • Неблокирующий ввод/вывод (ввод/вывод)

Сейчас мы рассмотрим только обратные вызовы.

Функция обратного вызова

Обратный вызов — это функция, которая является аргументом другой функции.

Из-за особенностей JavaScript вы не можете вызывать все функции одновременно. Если вы помните, для каждой функции у нас есть отдельный контекст выполнения функции и они нагромождены друг на друга.

Функции не выполняются одновременно. Они выполняются в зависимости от того, где и как мы их вызываем. Можете ли вы угадать результат здесь?

Я создавал функции в одном порядке, но вызывал их в другом.

Что сейчас произойдет?

Он будет записывать на консоль номера два, один и три, потому что я вызываю их в таком порядке.

Но что произойдет на этот раз?

Я вызываю все функции еще до их создания.

Выдаст ли это ошибку?

Нет, результат будет тот же: два, один и три.

Если вы не понимаете почему, я рекомендую вам вернуться к объяснению контекста выполнения.

Главным выводом здесь был не контекст выполнения, а тот факт, что результаты предоставляются в соответствии с порядком, в котором я назвал функции.

Но что, если возникнет ситуация, когда вы захотите вызвать функцию только тогда, когда сработала предыдущая функция?

Представьте себе реальный пример: ресторан.

№1 Вы идете в ресторан, и кассир принимает ваш заказ

#2 После того, как кассир примет ваш заказ, вам сообщат номер заказа.

№3 Вы садитесь и ждете заказа

#4 Когда еда будет готова, кассир позвонит вам по номеру заказа.

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

Чтобы кассир вам позвонил, они зависят от другой функции – подготовки заказа. Поэтому они ждут другого человека.

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

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

Они не могут вам позвонить сразу после того, как вы сделаете заказ, но пока вы ждете, они готовят заказ. С другой стороны, им также необходимо знать, как связаться с вами, пока вы ждете за своим столом. Вот почему у вас есть номер заказа, это способ связаться с вами.

Обратный звонок — это способ перезвонить. Итак, когда заказ готов, они используют этот обратный вызов, чтобы связаться с вами и попросить принять заказ.

Функция приготовления еды выполняется, и когда она будет завершена, она автоматически будет готова использовать номер вашего заказа, чтобы связаться с вами и попросить вас принять заказ.

Короче говоря, вы присоединяете одну функцию к другой функции. Вот минималистичный пример:

Чтобы было легче понять, начните читать с самой последней строчки.

Когда приближается покупатель, я вызываю функцию order с тремя аргументами: макароны, которые являются параметром еды в самой первой строке кода.

987 — это значение идентификатора, а обратный вызов — это функция callCutomer.

Внутри заказа, прежде всего, я написал, что получил заказ на конкретное блюдо, в данном случае макароны.

Затем мы представляем, что было приготовление еды и процесс ожидания.

Затем мы вызываем функцию callCustomer и передаем идентификатор. Это то, что делает функция обратного вызова. Он ждет приготовления еды и, когда она будет готова, звонит покупателю.

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

Вместо этого в функции заказа я присоединяю функции и мне больше не приходится это контролировать самому.

Наряду с обратными вызовами часто возникает проблема с так называемым обратным адом, о котором я рекомендую прочитать позже: https://dev.to/catherineisonline/what-is-callback-hell-3emp.

IIFE

Немедленно вызываемое функциональное выражение — это функция, которая вызывается в момент ее определения. На этот раз нам не нужно сохранять ее ни в каких переменных, и она останется анонимной функцией.

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

Давайте воссоздадим мою функцию отправки сообщения еще раз:

Чтобы написать IIFE, вы просто пишете функцию без имени, но чтобы не получать ошибку, как раньше, вы заключаете ее в круглые скобки, чтобы она рассматривалась как выражение, а затем немедленно вызываете ее, передавая аргумент.

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

В функции не обязательно использовать какие-либо параметры, это не обязательно. Это зависит от того, чего вы пытаетесь достичь.

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

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

Зачем нам нужен IIFE?

Есть несколько причин, почему IIFE полезен.

  • Частная область: создаваемые нами переменные будут как бы «застревать» внутри IIFE, поскольку они недоступны в контексте выполнения.

Он не сохраняется в глобальном контексте выполнения или контексте выполнения функции.

Это дает вам гарантию, что переменные не будут использоваться повторно или каким-либо образом затронуты. Это особенно важно, когда речь идет о var.

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

Но когда дело доходит до IIFE, даже var не сможет ускользнуть:

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

  • Инкапсуляция. Помимо переменных, вы также можете создавать модули, которые содержат как личную, так и общедоступную информацию в одном месте. Вместо того, чтобы все разделять, вы просто сохраняете все в одном IIFE.

В приведенном выше примере, хотя я объявил переменную и функцию внутри IIFE, он возвращает мне объект, который будет сохранен в messageModule.

Другими словами, я присвоил возвращаемое значение переменной messageModule.

Это способ инкапсулировать частную и общедоступную информацию вместе.

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

  • Передача параметров. Кроме того, вы можете передавать глобальные переменные в IIFE и выполнять над ними операции конфиденциально.

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

Давайте представим себе более реальные примеры.

Например, вы работаете с API погоды, который извлекает данные.

Вы хотите создать повторно используемый блок кода, который также инкапсулирует этот запрос API.

Это может привести к нескольким преимуществам.

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

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

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

Продолжим работу с API погоды.

Изображение, что мы хотим создать IIFE и собираемся передать ему ключ API.

Передавая этот ключ, мы собираемся инициализировать функцию.

Я буду создавать IIFE шаг за шагом. Сначала давайте создадим основы:

Что я здесь сделал?

У меня есть IIFE, который я сохраняю в переменной apiRequest. Все, что находится в теле функции, останется приватным и не будет доступно извне. Все, что я напишу в операторе возврата, будет раскрыто.

В качестве аргумента в конце IIFE я передаю воображаемый ключ API, чтобы можно было сделать запрос API.

Внутри этой функции у меня есть еще одна функция с именем findCountry, которая должна получать название страны и выводить на консоль столицу этой страны.

Это будет сделано с помощью воображаемого запроса API.

Затем я предоставляю ту же функцию findCountry внутри оператора return, и она становится доступной.

В результате, когда я вызываю эту функцию вне IIFE, она записывает в консоль результат своего тела.

Затем внутри IFFE рядом с функцией findCountry я создам еще одну, но на этот раз я не собираюсь раскрывать ее при возврате.

Эта функция получает информацию о стране, выполняет воображаемый запрос API и возвращает некоторые данные.

Затем мы возвращаем страну, которую искали, и столицу, которая была возвращена благодаря запросу API.

В функции findCountry мы вызовем этот API fetchCountries, который в настоящее время является закрытым, и попытаемся получить возвращаемую информацию.

Что должно произойти, когда мы попытаемся получить данные о странах? Функция fetchCountries получает информацию о стране, затем ищет страну и взамен предоставляет нам данные с названием страны и столицей, которые сохраняются в переменной данных.

Вот полный код:

Таким образом, запрос API внутри fetchCountries остается закрытым, и в то же время мы можем повторно использовать эту функцию для других API.

Обратите внимание: это нереалистичный способ выполнения запросов к API, и он очень минималистичный, просто чтобы понять, как можно использовать IIFE, а не как использовать сам API.

Этот пример иллюстрирует, как можно сделать запрос API конфиденциальным и безопасным, а также отделить его от другого кода, что значительно облегчит его последующую отладку.

Пора вспомнить, на чем мы остановились. Все это мы начали, когда дело дошло до определения функции.

Распространенные способы определения функции:

  • Объявление функции
  • Выражение функции
  • IIFE
  • Методы
  • Конструктор функций
  • Выражение стрелочной функции
  • Функция генератора

Методы

Методы также являются функциями. Функция — это блок кода, который должен выполнять определенную задачу (задачи). Метод также является функцией, которая должна выполнять задачу(и), но она связана с объектом. Другими словами, методы — это тоже функции, но свойства объекта.

Что такое недвижимость?

Свойство — это пара ключ-значение объекта, другими словами, объекты представляют собой коллекции этих свойств.

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

Однако методы не могут существовать сами по себе, и они должны быть свойством чего-то другого, в данном случае объектов.

Они определяют поведение объекта, в котором находятся, и воздействуют на другие свойства этого объекта.

Вот почему вызовы функций и вызовы методов также различны.

Чтобы вызвать функцию, вы просто используете имя функции, однако для вызова метода вам необходимо использовать имя объекта вместе с точечной записью.

Встроенные методы

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

Встроенные методы — это заранее написанные методы, являющиеся частью JavaScript.

Эти методы выполняют обычные задачи без необходимости писать все с нуля.

Встроенные методы экономят много времени и обеспечивают гарантированную функциональность.

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

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

На MDN очень, очень много объектов, о которых вы можете прочитать подробнее.

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

Встроенный строковый метод

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

Вот пример:

Вот так, это так просто. Мы взяли имя строки и с точкой в ​​нотации использовалось имя метода, а также передавалось все, что мы искали.

Встроенный метод Number

Существует множество полезных методов работы с числами.

Один из них может принимать число и возвращать фиксированную сумму дробной части. Итак, если у меня 9,9, он должен вернуть 9, если 1,145, он должен вернуть 1.

Вы просто указываете нужное число, добавляете метод и получаете результат. Не нужно создавать какую-либо логику самостоятельно.

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

Функции конструктора

Функция-конструктор, также известная как просто конструктор, — это специальная функция, создающая объект.

Вы можете создать несколько экземпляров (объектов) определенного типа, где каждый экземпляр наследует свойства или методы, определенные внутри конструктора.

Чтобы отличить обычные функции от конструкторов, имя конструктора следует начинать с заглавной буквы.

В этом примере мы создали функцию-конструктор Person, которая принимает два параметра, а затем назначает их в качестве свойств.

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

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

Ключевое слово this будет относиться к объекту при его создании. Если вы не знакомы с тем, что это делает, я вернусь к этому во время функций стрелок.

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

В этом примере, когда мы создавали customerOne, мы не создавали для него никаких методов. Но учитывая, что он создан с помощью конструктора Person, он должен наследовать его методы.

Что произойдет, если мы попытаемся вызвать методsayHi для customerOne?

Он зарегистрирует в консоли «привет», поскольку он был унаследован автоматически.

После того, как вы создали новый объект, вы также можете добавить к нему новые свойства, однако другие объекты, даже если они созданы из того же конструктора, не будут наследовать это свойство.

Чтобы создать новое свойство, вы можете использовать точечную запись, имя нового свойства и равенство значению.

В этом примере мы добавили в customerOne новое свойство age. Однако это свойство будет принадлежать только customerOne и не будет наследоваться другими объектами, созданными из того же конструктора.

Чтобы создать метод для новых объектов, вы можете использовать аналогичную структуру.

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

Добавим возраст в конструктор:

Теперь каждый объект будет иметь свойство возраста.

Интересный вопрос — что произойдет, если я не укажу значение возраста при создании нового человека?

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

Если вы попытаетесь войти в консоль CutomerOne, он покажет, что возраст не определен.

Итак, это может быть хорошо, потому что мы не получили никакой ошибки.

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

На этот раз, если клиент пропустит поле возраста, значение по умолчанию будет установлено на «-». Это более современный способ, но в качестве альтернативы вы также можете условно проверить, является ли значение неопределенным, и, если да, установить значение по умолчанию.

Здесь мы условно установили значение по умолчанию. Свойство age будет равно параметру age, если age не равно неопределенному. Если да, то значение будет равно «-».

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

Пора вспомнить, на чем мы остановились. Мы начали все это, когда дело дошло до определения функции.

Распространенные способы определения функции:

  • Объявление функции
  • Выражение функции
  • IIFE
  • Методы
  • Функции конструктора
  • Выражение стрелочной функции
  • Функция генератора

Мы уже рассмотрели объявления функций, выражения функций и IIFE. Пришло время понять выражения стрелочных функций.

Выражения функций со стрелками

Теперь, когда мы знаем, что такое функциональные выражения, будет гораздо проще понять, что такое стрелочная функция.

Стрелочная функция — это более современная альтернатива традиционным функциональным выражениям.

Преимущества функции стрелки:

  • Читабельный, простой, однострочный синтаксис: вы можете опустить ключевое слово function, фигурные скобки и даже возврат.

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

В противном случае вам придется использовать брекеты.

Когда дело доходит до аргументов, вы можете избежать заключения аргумента в фигурные скобки. Но сделать это можно только тогда, когда аргумент всего один.

  • Лексическая привязка «this». Стрелочные функции не имеют собственного контекста «this» и наследуют его от области, в которой они расположены. Это упрощает использование «this», когда дело доходит до функций обратного вызова.

Вот пример стрелочной функции, которую мы используем в классе:

У нас есть класс Counter, у которого есть счетчик методов.

В этом методе счетчик увеличивается на 1 каждый раз при его вызове, и мы записываем текущий счетчик на консоль.

Он выполняется с помощью интервальной функции, которая постоянно вызывается в конструкторе.

Интервал имеет функцию обратного вызова, которая запускается только через 1 секунду. В этом случае интервал и счетчик являются стрелочными функциями и используют ключевое слово «this».

Традиционно это относится к контексту, в котором она используется. В случае стрелочных функций это относится к контексту, которым она окружена, контексту класса Counter, вот и все.

Что произойдет, если мы преобразуем интервальную функцию и функцию обратного вызова внутри нее в обычную функцию?

Я изменил только функцию интервала, и это больше не функция стрелки.

Для понимания я собираюсь немного изменить код.

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

Затем я регистрирую в консоли еще одно ключевое слово this внутри функции обратного вызова, которая запускается через 1 секунду.

Вернет ли это другие результаты?

Первое это:

Второе это:

Как видите, первое относится к контексту счетчика, а второе — к глобальному объекту (окну).

Но почему?

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

Когда движок браузера читает JavaScript, он создает контекст выполнения, например глобальный контекст выполнения, а затем для каждой функции существует контекст выполнения функции.

Все это накладывается друг на друга, образуя стек вызовов.

Если это обычная функция и вы вызываете ее обычным способом, она будет выполняться в том порядке, в котором вы ее написали.

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

Функции обратного вызова — это функции, передаваемые в обычные функции, которые также называются функциями высшего порядка.

У них есть приоритет, как и у переменных, они — граждане первого сорта.

Но функция обратного звонка — это счастливый друг, которого пригласили на эту особенную вечеринку. А то, как пройдет вечеринка, зависит от функции высшего порядка, а не от функции обратного вызова.

И ЭТА партия не исключение.

Это означает, что для того, чтобы понять, к чему относится это ключевое слово, нам нужно понять, кто главный герой.

В нашем случае функция обратного вызова вызывается внутри функции интервала. Интервал вызывается в конструкторе. Ключевое слово this в конструкторе относится к экземпляру класса Counter.

This будет автоматически привязан к счетчику, поэтому первое значение this относится к счетчику.

Когда мы вызываем функцию обратного вызова setInterval, у нее нет контекста, а когда контекста нет. В результате контекст по умолчанию будет установлен как глобальный контекст.

Если мы используем строгий режим, то вместо глобального контекста будет установлено значение неопределенное.

Давайте снова напишем тот же класс, но на этот раз я попытаюсь реализовать счетчик, как и раньше, но снова без стрелочной функции:

Что случится?

Это выдаст ошибку, поскольку это относится к глобальному объекту и не может найти функцию счетчика.

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

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

Но что делали люди до того, как стрелка заработала?

Очевидно, есть и другие решения.

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

Чтобы решить эту проблему, вы можете просто использовать метод bind(). Это метод, который может манипулировать тем, к чему относится this.

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

  • Нет аргументов. Стрелочные функции не имеют собственных аргументов, поскольку они наследуют их от окружающей области.

Это помогает избежать проблем с затенением переменных.

Минусы стрелочной функции

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

Могут быть сценарии, когда нам понадобится доступ к ключевому слову this функции.

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

В каких сценариях нам нужен доступ к собственной функции this?

  • Методы объекта

Когда дело доходит до методов, обычно нам необходимо получить доступ к другим свойствам объекта, в котором этот метод существует.

Это означает, что когда мы попытаемся использовать это ключевое слово, стрелочные функции вызовут проблему.

Почему?

Потому что это ключевое слово будет относиться не к окружающей области этого объекта, а к глобальному объекту.

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

Вот пример для лучшего понимания:

  • Функции конструктора

Аналогичная проблема с этим ключевым словом возникает, когда речь идет о функции-конструкторе.

Вот почему мы не используем стрелочные функции в функциях-конструкторах, поскольку ссылка на это ключевое слово очень важна.

Если мы попытаемся использовать стрелочную функцию во втором примере, она выдаст ошибку TypeError «Клиент не является конструктором».

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

  • Обратные вызовы

Как и в случае с методами, аналогичные проблемы возникают, когда мы используем функции обратного вызова.

Пока функция обратного вызова окружена объявлением функции, она будет работать нормально. Даже если сама функция обратного вызова является стрелочной функцией.

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

  • Обработчики событий

События в JavaScript — это различные операции, которые происходят при определенных условиях, таких как загрузка страницы, нажатие пользователем определенной кнопки, прокрутка пользователем и т. д.

Когда мы работаем с событиями, мы используем функцию обратного вызова — функцию, которая следует за операцией (клик, загрузка, прокрутка).

При использовании ключевого слова this мы должны быть уверены, что его ссылка не выходит за пределы контекста.

Мы должны быть уверены, что это относится к области, которой оно окружено.

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

В этом примере мы нацелены на кнопку с корневым идентификатором.

Первое событие реализовано с помощью обычной функции, а второе — стрелочной функции.

Каков будет результат?

При первом нажатии отобразится кнопка, поскольку это ключевое слово относится к кнопке.

Во втором случае он вернет объект Window, поскольку он вышел за пределы области видимости.

Функция генератора

Функция-генератор — это специальная функция, позволяющая контролировать процесс выполнения — вы можете приостанавливать и возобновлять его, генерируя значения одно за другим!

Чтобы создать функцию-генератор, вам нужно добавить звездочку (*) после ключевого слова функции, а внутри тела вы используете ключевое слово выход, которое приостанавливает процесс выполнения для возврата «выданного» значения.

Затем функцию можно возобновить для получения следующего значения.

Синтаксис немного отличается, но в целом очень похож:

Это не имеет большого смысла, но пойдем дальше.

Когда вы вызываете функцию-генератор, она не выполняется сразу, как обычная функция.

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

Итератор — это своего рода инструмент или функция в JavaScript, которая позволяет нам просматривать каждое значение по одному в различных коллекциях данных.

Объект Iterator имеет различные методы, которые помогают нам контролировать процесс выполнения функции.

Давайте сохраним функцию генератора и проверим, что она создает:

Проверив консоль, мы получаем следующее:

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

Таким образом, он не выполняет функцию, а является инструментом для выполнения функции в будущем.

Генераторы могут иметь несколько статусов:

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

Методы генератора

Методы-генераторы — это действия, которые помогают нам выполнить функцию и получить от нее результат.

Для начала давайте сохраним наш генератор в переменной:

Далее давайте начнем получать результаты, используя один из методов под названием next().

Этот метод даст только первый урожай, а не все.

После того, как мы получим первый результат, выполнение приостановится.

В результате мы получим не просто 0, а объект с двумя парами ключ-значение:

#1 Свойство value — это результат, который нам нужно получить. Если выдать нечего и мы все равно пытаемся использовать следующий метод, он вернет неопределенное значение.

#2 Свойство Done представляет собой логическое значение, которое указывает, завершила ли функция выполнение и есть ли еще результаты для получения. Если свойство Done имеет значение false, это означает, что у нас есть больше выходных данных, в противном случае оно истинно.

Следующий метод

Вместо того, чтобы регистрировать это в консоли, давайте сохраним каждый выход и покажем их все:

В результате мы увидим следующее:

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

В этом примере мы передали число и в результате вернули двойную сумму. Вы можете передать столько аргументов, сколько захотите.

Доходность или доход*

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

Когда мы добавляем звездочку (*) в конце доходности, она будет перебирать данные, которые мы написали после нее, и возвращать каждое значение, пока оно не будет выполнено. Обычно мы добавляем данные, которые можно перебирать.

В результате вы увидите 1, 2, а затем 3.

Другой способ использования доходности* — это делегирование доходности других генераторов. Таким образом вы можете объединить несколько разных генераторов, которые принесут один результат или просто сделать структуру более организованной:

В результате мы получим «привет», а затем «там».

Метод возврата

Следующий метод, который вы можете использовать, — это возврат, который завершает выполнение, и если вы попытаетесь использовать следующий метод снова, он всегда будет возвращать неопределенное значение. Для свойства Done будет установлено значение true.

Давайте добавим еще немного кода в наш предыдущий генератор:

Я добавил новую переменную, в которой использовал метод возврата. В результате вы увидите следующее:

Кроме того, мы можем передать значение методу return и вместо возврата значения undefined мы можем передать окончательное значение, которое хотим вернуть.

Давайте попробуем:

Как вы думаете, какой будет результат?

Как видите, когда мы вернули окончательное значение, оно теперь заменено на Termination, однако следующие значения остаются неопределенными.

Метод throw

Метод throw используется для прекращения выполнения и создания исключения.

Исключение — это событие, которое является неожиданным, чаще всего это ошибка, которую мы не ожидали.

Для обработки таких ошибок нам нужно использовать блок try…catch. Этот блок представляет собой оператор JavaScript, в котором в блоке try мы помещаем код, который, как мы подозреваем, может вызвать исключение, а в блоке catch мы обрабатываем ошибку.

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

В результате использовать метод throw вместе с try..catch — отличная идея, потому что мы можем справиться с потоком ситуации и обработать ошибки.

Давайте реализуем метод throw:

В этом примере мы получаем первый результат, после чего выбрасываем исключение.

Используя новую ошибку, мы создаем объект ошибки и добавляем собственное сообщение.

Далее мы пытаемся снова получить результат.

Что случится?

Мы действительно успешно получаем первый результат, а затем выдаем ошибку. Из-за этой ошибки выполнение прекращается, а второй выход не определен.

Почему?

Потому что казнь остановлена ​​и остальное уже нельзя возобновить.

Плюсы и минусы функций-генераторов

Плюсы

  • «Лениное» выполнение: это означает, что эти функции выполняются только при вызове следующего метода. Это может быть полезно, когда вам не нужны все значения.
  • Состояние: их состояние остается прежним, пока вы продолжаете вызывать следующее значение, и это упрощает процесс создания собственных итераторов. Так легче контролировать поток.
  • Работа с бесконечностью. Учитывая, что вы сами контролируете, когда получить значение, а когда остановиться, это упрощает работу с бесконечными последовательностями, такими как натуральные числа, последовательности Фибоначчи или, например, случайно сгенерированные числа.
  • Экономия памяти: вместо извлечения тонн данных вы экономите много памяти, предоставляя только часть значений вместо хранения всей коллекции данных.

Минусы

  • Труднодоступность: невозможно выполнить несколько итераций параллельно, что иногда может нам понадобиться. Вы также не можете получить любой случайный элемент, и мы не можем начать с какой-либо части кода.
  • Сложность: хотя кажется, что у нас много контроля, в то же время это добавляет дополнительный уровень сложности. Нам нужно расширить нашу логику большим количеством кода и дополнительным выходом, выходом, выходом.
  • Никакого использования в обратных вызовах. В обратных вызовах мы можем использовать генераторы, но это не лучшая идея. Основная идея генераторов — сделать асинхронный код похожим на синхронный. Когда асинхронными функциями сложнее управлять, иногда могут пригодиться генераторы. Неправильное использование может привести к аду обратных вызовов и еще большей сложности. Вместо этого лучше использовать современный async/await.

Использование функций генератора

Я думаю, даже после рассмотрения плюсов и минусов генераторов может быть трудно понять, нужны ли они нам вообще.

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

Давайте возьмем несколько вариантов использования и попробуем понять, действительно ли нам нужны генераторы.

Циклы, которые можно приостановить и возобновить

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

Одной из причин называют внутреннее государственное управление. Это означает, что когда вы приостанавливаете циклы с помощью выхода, вы можете продолжить цикл именно с того места, на котором остановились.

Означает ли это, что только генераторы могут это сделать? Точно нет.

Есть много других способов добиться этого.

Async/await с пользовательским итератором

Во-первых, давайте определим собственный объект-итератор, который действует так, как если бы он был асинхронным.

Затем внутри него мы создадим свойство current, определяющее текущее состояние.

Далее мы добавим метод next, который ожидает 1 секунду, прежде чем продолжить. Это будет моделировать асинхронную работу и искусственно задерживать ответы.

Наконец, мы вернем объект, аналогичный ответу генератора.

Пришло время реализовать следующий метод.

Мы создадим асинхронную функцию, которая взаимодействует с нашим итератором и возвращает объект результата.

Она будет асинхронной, потому что мы не хотим, чтобы она блокировала другую асинхронную операцию, которую мы искусственно выполнили в итераторе.

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

Мы также добавляем цикл for, который будет выполняться пять раз, таким образом мы говорим, что хотим вызвать воображаемый метод next() 5 раз.

Далее, чтобы все заработало, нам просто нужно вызвать функцию:

Как и ожидалось, мы получили аналогичный результат и можем контролировать выполнение цикла:

Давайте сравним это с функцией генератора и воссоздадим все вышеперечисленное в генераторе:

В этом примере мы создали массив, аналогичный нашему циклу for. Затем мы добавили функцию-генератор, в которой мы перебираем массив и получаем каждый элемент.

Вот результаты:

Точно так же, как и в предыдущем случае.

Визуально, по объёму кода, лично мне кажется, что почти одинаково, символы можно посчитать, если хотите.

Кроме того, функция генератора кажется менее сложной и ее как будто легче понять.

Главный вопрос, который у меня возник по поводу генераторов, заключается в том, в каком реальном случае я могу их использовать?

Я нашел несколько ответов и реальные примеры генераторов:

  • Обработка больших данных или работа с большими данными по одному, когда вы не хотите влиять на память.

Один из примеров — работа с API при реализации нумерации страниц.

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

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

  • Потоковые данные — это данные, которые вы продолжаете получать бесконечно, скажем, ту же самую аналитику в реальном времени, курсы валют или данные фондового рынка, ленты социальных сетей в реальном времени и так далее.

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

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

Можете ли вы заменить поколения для таких функций?

Да.

Вы можете использовать async/await и промисы, как мы делали раньше.

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

Если дело доходит до потоковой передачи данных, существует множество альтернатив, таких как потоки Node.js, Websocket API и очереди сообщений, такие как Apache Kafka, а также множество потоковых библиотек.

Для планирования задач у нас также есть множество альтернатив, таких как очереди задач и различные планировщики.

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

Унарные функции

В JavaScript унарная функция — это функция, которая принимает только один аргумент и преобразует его значение.

Хотя это очень простой пример, существует множество различных ситуаций, в которых мы можем использовать унарные функции.

Встроенные методы

Одним из примеров, когда у нас часто встречаются унарные функции, являются встроенные методы. Существуют методы, которые принимают одно значение и выполняют над этим значением какую-то операцию.

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

Помимо функций, у нас есть множество унарных операторов, очень похожих на функции.

Основное различие между функциями и операторами в этом сценарии заключается в том, что операторы существовали в JavaScript раньше, чем функции, поскольку его конструкция состояла из операторов, вдохновленных языками C-стиля. Операторы в таких языках программирования — это символы или ключевые слова, которые выполняют операции, как и функции.

Вот почему сейчас в JavaScript есть много унарных операторов.

Давайте рассмотрим несколько примеров унарных операторов:

Это несколько примеров для общего понимания.

Оператор приращения увеличивает число на единицу, затем уменьшает на единицу, отрицание может преобразовать число в отрицательное значение, а оператор плюс часто используется для преобразования значения в числовое значение.

Когда полезны унарные функции?

Унарные функции могут быть полезны в двух ситуациях:

  • каррирование
  • Функции высшего порядка

Как мы уже рассмотрели функции высшего порядка, это функции, которые получают функции в качестве аргумента и/или возвращают другие функции.

Это также хороший пример того, где мы можем использовать каррирование:

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

Функции каррирования

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

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

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

Самая верхняя функция получает один аргумент и возвращает другую функцию, которая ожидает еще один аргумент.

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

Давайте получим результаты:

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

Количество аргументов, которые принимает функция, часто называют арностью.

Если функция принимает 2 аргумента, это двухарность, если 3, то трехарность и так далее.

Мы также можем преобразовать ту же функцию, каррируя, в стрелочную функцию:

Все осталось по-прежнему, мы просто использовали меньше кода, и теперь оно выглядит намного чище.

Когда использовать каррирование функций?

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

Композиция функций — это объединение нескольких функций для создания новой функции.

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

Представьте, что вы хотите выполнить две разные операции.

-В одном случае вы хотите взять значение, увеличить его на единицу, а затем удвоить.

-В другой операции вы хотите взять значение, возвести его в квадрат, а затем вычесть из него два.

Вместо того, чтобы писать множество функций для каждой операции, мы можем скомпоновать несколько функций вместе!

#1 Во-первых, мы можем составить композицию приращения и удвоения.

#2 Затем мы можем составить возведение в квадрат и вычитание двух.

Если вы заметили, между этими двумя вариантами также есть сходство.

Они оба примут одно значение и оба выполнят два действия.

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

В нашем случае нам нужна функция, которая получает две функции, верно?

Функция выше — это функция многократного использования, которая получит две функции — func1 и func2.

Параметр «a» — это значение, которое получит наша функция.

В результате сначала он выполнит операцию над первой функцией func1, а результат func1 будет передан второй функции func2.

Таким образом, результат композиции функции будет результатом второго аргумента функции, который выполняет операцию над результатом первого аргумента функции.

Давайте разберемся, что происходит в приведенном выше коде.

Функциональная композиция предполагает наличие двух функций.

Мы собираемся достичь первой цели — увеличить значение на единицу, а затем удвоить его, поэтому нам нужны две функции. Эти две функции, которые нам нужны, — add и double.

Далее нам нужно передать эти функции в нашу функциональную композицию.

Но, как видите, мы еще не передали никаких аргументов. Давайте проверим значение addThenDouble:

Значение представляет собой ссылку на функцию, оно пока не выполняет никаких вычислений.

Значение, которое мы сохраняем сейчас, — это всего лишь ссылка на функцию. Нам нужно вызвать эту функцию.

Вот почему в консоли мы вызываем функцию addThenDouble и передаем значение, с которым хотим работать.

В результате мы получим 6.

Почему?

Потому что func1 добавит 1 к аргументу со значением значения 2. В результате мы получим 3, а затем func2 удвоит результат функции. Расчет будет 3*2=6.

Еще один пример:

Не запутайтесь и помните, что самая первая функция, композиция создается всего один раз.

Мы лишь воссоздали те функции, которые он получит.

Хорошо, что происходит в приведенном выше коде?

Наша вторая цель — возвести значение в квадрат, а затем вычесть из него два.

Соответственно, мы создаем две функции, которые позже передаем в композицию функции и сохраняем ее в переменной с именем SquareThenSubtract.

Далее передаем значение и в результате получаем 7.

Почему?

Потому что первая функция будет квадратом 3, то есть 9, а затем мы вычитаем 2, и получится 7.

Вы можете спросить, является ли это единственным решением для композиции функций?

Нет, существуют другие способы обработки таких функций.

Одним из способов реализации композиции функций является служебная библиотека Lodash, которая предоставляет различные функции для выполнения различных задач, одной из которых является компоновка.

Эта функция работает так же, как функция каррирования, которую мы написали сами.

Лучше это или хуже, лично я ДУМАЮ, что это зависит от разных факторов — ваших предпочтений, предпочтений компании, целей и структуры кода.

Если вы используете библиотеку только для этой функции, действительно ли вы хотите, чтобы она занимала дополнительное пространство? Или, может быть, вы предпочитаете иметь больше контроля над составом функций и предпочитаете создавать его самостоятельно? Я сам особо не использовал каррирование и на данный момент у меня нет однозначного мнения по этому поводу, но я считаю, что это зависит от многих вещей.

Дайте мне знать в комментариях, что вы думаете по этому поводу.

Чистые функции

Чистые функции в JavaScript — это функции, которые всегда возвращают один и тот же результат для одного и того же ввода и не имеют побочных эффектов.

Что такое побочный эффект?

В Javascript побочный эффект — это блок кода, который делает что-то кроме выдачи результата. Это код, который работает и влияет на что-то за пределами области действия. Допустим, у вас есть кнопка, которая должна обновлять определенный номер, но помимо этого номера она также обновляет что-то еще. В таком случае мы говорим, что эта функция имеет побочный эффект.

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

Любая функция, не имеющая побочного эффекта, является чистой функцией. Если функция имеет побочный эффект, то это нечистая функция, следовательно, пока вы понимаете побочные эффекты, вы понимаете чистые функции.

Один из наиболее распространенных побочных эффектов возникает, когда функция зависит от переменной, которая существует вне ее области действия.

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

Другой распространенный побочный эффект — использование журнала консоли в функции. Почему? Потому что консоль тоже является внешним объектом, похожим на переменную count.

Еще один побочный эффект, с которым мы часто сталкиваемся, — это манипулирование DOM. DOM (объектная модель документа) — это интерфейс, который вы видите при отображении веб-страницы. С помощью JavaScript мы можем выполнять различные операции, которые могут манипулировать DOM. Вы можете добавлять, обновлять или удалять различные элементы, будь то div, верхний или нижний колонтитул. Вы также можете изменить содержание абзацев. Все это побочный эффект, потому что вы меняете вне функции что-то, что этой функции не принадлежит.

Работа с API — еще один распространенный побочный эффект. Это потому, что работа с внешними ресурсами находится вне нашего контроля, и в этом весь побочный эффект.

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

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

Функции верхнего уровня

В JavaScript вы время от времени будете слышать термин «функция верхнего уровня». Это не очень популярно, но полезно знать. Функция верхнего уровня — это просто функция, расположенная в глобальной области видимости. Это высшая функция. Он не обернут другими функциями, не находится внутри каких-либо объектов, классов или какого-либо «интерфейса». Другими словами, они расположены непосредственно в глобальном объекте, известном как объект окна.

Вложенные функции

Вложенные функции — это функции, которые мы создаем внутри других функций. Другими словами, вложенные функции — это функции, вложенные в другие функции.

Это помогает инкапсулировать код и функциональность в функциональные блоки.

В приведенном выше примере функции верхнего уровня вложенной функцией является функция notTopLevel.

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

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

Рекурсия

Рекурсия в Javascript — это метод, при котором функция вызывает саму себя для выполнения определенной задачи.

Функция может продолжать вызывать себя до тех пор, пока мы не укажем, что она достигла конечной точки. Чтобы избежать бесконечной рекурсии, мы предоставляем так называемый базовый вариант. Базовый случай — это условие(я), которое останавливает рекурсию.

Рекурсия часто используется в более сложных вычислениях, которые разбивают большие задачи на более мелкие.

Давайте создадим простую рекурсивную функцию, чтобы проиллюстрировать, как она работает. Это простой пример, поскольку рекурсия часто используется в более сложных вычислениях.

Изображение, мы хотим вычислить сумму всех положительных чисел от единицы до предоставленного нами числа.

Если предоставленное число равно 4, мне нужна сумма 1, 2, 3 и 4.

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

Если мы передаем 4, мы возьмем это 4 и, чтобы добавить предыдущее число, оно будет 4–1, то есть будет 3, затем 3–1 составит 2 и так далее.

Если так будет продолжаться, мы можем уйти в минус. Вот почему при создании рекурсии нам нужно подумать о базовом варианте, который в какой-то момент остановит эту рекурсию.

Поскольку мы решили работать с положительными числами, это означает, что нам просто нужно остановить рекурсию, когда мы достигнем числа 1. Мы не хотим, чтобы 1 опускалась ниже единицы.

Давайте разберемся в описанном выше процессе.

-Функция getSum получает число 4. Проверяет, равно ли оно 1, и если нет, то переходим к блоку else.

-Функция возвращает 4 + результат, полученный в getSum, куда мы передаем 4–1.

-Каким будет результат 4 + результат getSum? Это будет 4 + 3 + снова результат getSum. Число в блоке if еще не равно 1, поэтому мы все равно переходим к блоку else.

-Каким будет результат 4 + 3 + результат getSum? Это будет 4 + 3 + 2 + снова результат getSum.

-Мы, наконец, достигаем точки, где переданный параметр «n» равен 1, поэтому getSum возвращает 1 и заканчивается на этом.

-В результате имеем 4+3+2+1, что дает 10.

Функция продолжает рекурсивно вызывать себя, пока мы не достигнем базового случая 1.

Когда использовать рекурсию?

Существует несколько вариантов использования рекурсивных функций.

  • Математические вычисления. Примерами использования рекурсии являются факториалы, последовательности Фибоначчи и возведение в степень.
  • Разделяй и властвуй. Алгоритмы, известные как «разделяй и властвуй», такие как быстрая сортировка или сортировка слиянием, являются примерами использования рекурсии.
  • Обход дерева. Двоичные деревья, деревья DOM или файловые системы являются примерами структур данных, в которых мы можем выполнять поиск в глубину или обход по порядку с помощью рекурсии.
  • Графики. Обнаружение циклов, поиск связных компонентов или проблемы, связанные с графами, часто решаются с помощью рекурсии.
  • Обратное отслеживание: судоку, гамильтонов цикл, поиск слов или подобные задачи, похожие на лабиринт, также можно решить с помощью рекурсии, используя технику обратного отслеживания.

Заключение

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