В JavaScript функции это значения: их кладут в переменные, передают аргументами и возвращают из других функций. На этом держатся методы массивов, обработчики событий, middleware, хуки React. Функция высшего порядка (higher-order function, HOF) принимает функцию или возвращает функцию.
Способы объявить функцию
function declared(a, b) { return a + b; } // объявление: поднимается целиком
const expr = function (a, b) { return a + b; }; // выражение: доступно после строки
const arrow = (a, b) => a + b; // стрелка: короткая, без своего this
Отличия стрелок: нет своих this, arguments, super, нельзя вызвать через new, нет свойства prototype. Для методов объектов и конструкторов они не подходят, для колбэков идеальны.
Параметры
- Значения по умолчанию срабатывают только для
undefined, не дляnull:f(null)оставитnull. - Rest-параметры
(...args)собирают остаток в настоящий массив. Старыйargumentsэто массивоподобный объект, и в стрелках его нет. fn.lengthэто число параметров до первого с дефолтом или rest. Им пользуются реализацииcurry.- Аргументы передаются по значению, но для объектов значение это ссылка: мутация объекта видна снаружи, переприсваивание параметра нет.
Функции высшего порядка в стандартной библиотеке
map, filter, reduce, some, every, find, sort, setTimeout, addEventListener, Promise.then принимают функции. Это делает код декларативным: вы говорите «что», а не «как».
const total = orders
.filter((o) => o.paid)
.map((o) => o.sum)
.reduce((acc, s) => acc + s, 0);
Возврат функции: фабрики и замыкания
const makeMultiplier = (k) => (x) => x * k;
const double = makeMultiplier(2);
double(21); // 42
Композиция
Композиция склеивает функции в конвейер: результат одной идёт на вход следующей.
const pipe = (...fns) => (x) => fns.reduce((v, f) => f(v), x);
const slugify = pipe(
(s) => s.trim(),
(s) => s.toLowerCase(),
(s) => s.replace(/\s+/g, '-'),
);
slugify(' Hello World '); // 'hello-world'
pipe применяет слева направо, compose справа налево.
Каррирование и частичное применение
Каррирование превращает f(a, b, c) в f(a)(b)(c). Частичное применение фиксирует часть аргументов и возвращает функцию от остальных: add.bind(null, 5) или (b) => add(5, b).
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) return fn.apply(this, args);
return (...more) => curried.apply(this, [...args, ...more]);
};
}
const sum3 = curry((a, b, c) => a + b + c);
sum3(1)(2)(3); // 6
sum3(1, 2)(3); // 6
Чистые функции
Чистая функция при одинаковых аргументах всегда возвращает одинаковый результат и не имеет побочных эффектов: не меняет внешние переменные и аргументы, не ходит в сеть, не пишет в DOM. Такие функции легко тестировать, кешировать и вызывать повторно. Компоненты React и редьюсеры должны быть чистыми.
Мемоизация
Мемоизация кеширует результат по аргументам. Работает только для чистых функций.
function memoize(fn) {
const cache = new Map();
return (arg) => {
if (!cache.has(arg)) cache.set(arg, fn(arg));
return cache.get(arg);
};
}
Нюансы: ключ по нескольким аргументам надо строить (например, вложенные Map), объекты сравниваются по ссылке, кеш растёт бесконечно без ограничения.
debounce и throttle
Две классические HOF для частых событий:
- debounce вызывает функцию, когда события прекратились на заданное время (поиск при вводе);
- throttle вызывает не чаще одного раза за интервал (scroll, resize).
Частые ошибки
['1', '2', '3'].map(parseInt)даёт[1, NaN, NaN]:mapпередаёт индекс вторым аргументом, аparseIntсчитает его основанием.- Передать
obj.methodкак колбэк и потерятьthis. - Мемоизировать нечистую функцию.
- Думать, что дефолт сработает для
null.
Что спросят на собесе
- Что такое функция высшего порядка, примеры из стандартной библиотеки.
- Чем каррирование отличается от частичного применения, напишите
curry. - Что такое чистая функция и зачем она нужна.
- Напишите
memoize,composeилиpipe. - Чем стрелочная функция отличается от обычной.