Всем привет! Давно не было постов, за что прошу прощения у каждого. И дабы смягчить свое наказание я подготовил для вас статью о функциях и секретах JavaScript, о которых возможно вы и не знали :)
Сегодня я дам несколько советов для продвинутой работы с функциями. Надеюсь, они вам пригодятся. В статье несколько разделов:
- Чистые функции
- Функции высшего порядка
- Кэширование функций
- Ленивые функции
- Каррирование
- Композиция функций
Чистые функции
Что такое чистая функция?
Функция называется чистой, если соблюдаются оба следующих условия:
- для одних и тех же аргументов она возвращает одно и то же значение;
- во время выполнения функции не возникает побочных эффектов.
Пример 1:
Если передавать в функцию одно и то же значение радиуса, она каждый раз будет возвращать одинаковый результат. Кроме того, при выполнении этой функции ничего не происходит за ее пределами. Поэтому эта функция является чистой.
Пример 2:
Эта функция-счетчик каждый раз будет возвращать разные результаты, поэтому чистой она не является.
Пример 3:
Если в функцию isMale из третьего примера передавать одно и то же значение аргумента, она будет возвращать один и тот же результат, но у нее есть побочные эффекты. В этом случае побочным эффектом будет изменение значения глобальной переменной maleCounter, то есть эта функция также не является чистой.
Зачем нужны чистые функции?
Зачем нам выделять чистые функции среди остальных? У них много преимуществ. Используя чистые функции, можно писать более качественный код.
1.Чистые функции более понятны, их легко читать.
Чистые функции всегда выполняют конкретную задачу и возвращают точный результат. Они существенно повышают читаемость кода и упрощают написание документации.
2.Компилятору проще оптимизировать чистые функции.
Рассмотрим фрагмент кода.
3. Чистые функции проще тестировать.
Тестирование чистых функций можно выполнять без учета контекста. При написании модульных тестов для чистой функции достаточно передать ей входное значение и с помощью assert проверить, соответствует ли результат выполнения функции нашим требованиям.
Рассмотрим простой пример. Чистая функция принимает в качестве аргумента числовой массив и увеличивает каждый элемент массива на 1.
Нам достаточно написать всего лишь вот такой модульный тест:
Если функция не является чистой, то нужно учитывать множество внешних факторов, а это задача не из легких.
Функции высшего порядка
Что такое функция высшего порядка?
Функция высшего порядка отвечает хотя бы одному из следующих критериев:
- принимает одну или несколько функций в качестве аргументов;
- возвращает другую функцию в качестве результата.
Использование функций высшего порядка повышает гибкость кода, благодаря чему мы можем сделать его лаконичным и эффективным.
Представим, что у нас есть массив целых чисел и нам нужно создать новый массив. Новый массив имеет столько же элементов, что и первоначальный, а значение каждого элемента нового массива в два раза больше значения соответствующего элемента первоначального массива.
Без функций высшего порядка мы бы написали такой код:
В JavaScript у объекта массива есть метод map().
С помощью метода map(callback) мы создаем новый массив, элементы которого являются результатами вызова заданной функции для каждого элемента первоначального массива.
Функция map — это функция высшего порядка.
Правильное использование функций высшего порядка повышает качество кода, поэтому в них стоит хорошенько разобраться. Все следующие разделы посвящены функциям высшего порядка.
Кэширование функций
Допустим, у нас есть следующая чистая функция:
Для повышения быстродействия программы мы хотим кэшировать результат выполнения функции, чтобы при последующих вызовах функции с теми же параметрами она уже не выполнялась, а результат возвращался непосредственно из кэша. Как этого добиться?
Мы можем написать функцию cached и обернуть в нее нужную нам функцию. Кэширующая функция принимает в качестве аргумента функцию, результат которой мы хотим получить, и возвращает новую функцию, заключенную в обертку. Внутри функции cached мы можем кэшировать результат предыдущего вызова функции, записав его в Object или Map.
Рассмотрим пример:
Ленивые функции
В теле функции иногда содержатся условные операторы, которые выполняются только один раз.
Мы можем повысить быстродействие программы, «удалив» такие операторы после первого выполнения, чтобы функции больше не приходилось выполнять их при последующих вызовах. В этом и заключается сущность ленивых функций.
Например, нам нужно написать функцию foo, которая будет всегда возвращать объект Date, полученный при первом вызове функции.
При каждом вызове этой функции необходимо проверять выполнение заданного условия. Если условие очень сложное, это может замедлить работу программы. Вот тут-то нам и пригодится концепция ленивой функции для оптимизации кода.
Можно записать функцию так:
После первого выполнения мы заменим первоначальную функцию новой. При последующих вызовах этой функции условие уже не будет проверяться — и код будет работать быстрее.
Теперь рассмотрим более практический пример.
При добавлении к элементу событий DOM необходимо обеспечить кросс-браузерную совместимость, в том числе с браузером IE. Для этого нужно определить среду браузера:
При каждом вызове функции addEvent нужно проверять выполнение условия. Используя ленивые функции, это можно сделать так:
Одним словом, если внутри функции есть условие, которое нужно проверять только один раз, мы можем оптимизировать код, написав ленивую функцию. После первой проверки первоначальная функция заменяется новой функцией, которая пропускает этап проверки условия.
Каррирование функций
Каррирование — это техника трансформации функции с несколькими аргументами в последовательность функций с одним аргументом.
То есть вместо того, чтобы принять все аргументы одновременно, функция сначала принимает первый аргумент и возвращает другую функцию, которая принимает второй аргумент; вторая функция в свою очередь принимает третий аргумент — и так до тех пор, пока не будут обработаны все аргументы.
Для чего это нужно?
- Каррирование позволяет избежать многократной передачи одной и той же переменной.
- Оно позволяет создавать функции высшего порядка. Оно существенно упрощает обработку событий.
- Небольшие фрагменты кода легко изменять и использовать повторно.
Рассмотрим простую функцию add. Она принимает три операнда в качестве аргументов и возвращает их сумму.
Вы можете передать в функцию слишком мало аргументов (и получить ошибочные результаты) или, наоборот, слишком много аргументов (тогда лишние аргументы будут проигнорированы).
Как каррировать эту функцию?
Код:
Пример:
Композиция функций
Предположим, нам надо написать функцию, которая работает так:
вводим bitfish, в результате получаем HELLO, BITFISH
Как видите, функция выполняет две задачи:
- конкатенация строк;
- перевод строки в верхний регистр.
Для этого можно написать следующий код:
В этом примере всего два этапа, поэтому в функции приветствия (greet) ничего сложного нет. Но если бы операций было больше, вызов функции greet содержал бы больше вложенных элементов и выглядел бы примерно так: fn3(fn2(fn1(fn0(x)))).
Для решения проблемы можно написать функцию compose, единственным предназначением которой будет композиция функций:
Таким образом, функцию greet можно реализовать через функцию compose:
При объединении двух функций в одну посредством compose код будет выполняться справа налево, а не изнутри наружу, что повышает его читаемость.
Однако сейчас функция compose поддерживает только два параметра, а нам нужна функция, которая сможет принимать любое количество параметров.
В известном проекте с открытым исходным кодом underscore функция compose реализована следующим образом.
Используя композицию функций, можно оптимизировать логические связи между функциями, улучшить читаемость кода и упростить его дальнейшее расширение и рефакторинг.