
Анонимные стрелочные функции в JavaScript, согласно некоторым опросам — самая популярная фича ES-2015, что также подчеркнуто исчерпывающим числом туториалов в интернете. Они бесспорно очень полезны, но в этой небольшой статье мы рассмотрим примеры использования обделенных вниманием не менее замечательных выражений с именованными функциями — NFE.
Короткая справка
Named Function Expression — расширение функциональных выражений в JavaScript, позволяющее именовать функцию, созданную как часть выражения (FunctionExpression):
let fe = function named(...) { /* function body */ };
Вся суть в том, что изнутри функции, на которую ссылается переменная fe, есть доступ к самой функции через имя named. Только изнутри функции и перезаписать имя нельзя!
Как это можно использовать
Например, нам нужно написать декорирующую функцию, которая будет считать количество вызовов какой-то функции. Это можно сделать весьма изящно с помощью NFE:
const count = f => function df() { df.calls = (df.calls || 0) + 1; return f(...arguments); };
Здесь мы получили доступ к возвращаемой функции df, чтобы при ее вызове сохранять счетчик в свойство calls, которое будет доступно для чтения при необходимости:
const csum = count((x, y) => x + y); csum(5, 10); // 15 csum(50, 1); // 51 csum.calls; // 2
Или сохраняем все результаты вызова функции:
const accum = f => function df() { let res = f(...arguments); (df.results = (df.results || [])).push(res); return res; };
В любом случае мы пользуемся тем, что функция в JavaScript — это объект, и мы можем дополнить его нужными свойствами в наших локальных задачах.
Очевидно, функцию мы можем и вызывать. Использовать NFE для рекурсии особенно приятно. Допустим, мы хотим найти n-ый член последовательности Фибоначчи (каждый почему-то рано или поздно этого хочет):
const rf = function getfn(n) { return n > 2 ? getfn(n - 2) + getfn(n - 1) : 1; }; rf(1); // 1 rf(10); // 55
Не стоит здесь обращать внимание на «нехвостоватость». А вот на возможность сделать то же самое через FunctionDeclaration — да. Но у функции-выражения есть одно преимущество: если вам захочется перенести функцию из rf в другую переменную, не потребуется править рекурсивный вызов внутри.
Аккуратный пример реализации таймера:
const ping = (callback, t) => setTimeout(function pf() { callback(); setTimeout(pf, t); }, t);
Здесь NFE мы используем литералом в качестве аргумента вместо объявления ненужной переменной. Почему не setInterval? Вместо callback’а здесь может быть ожидание резолва промиса перед следующим тиком таймера.
Интересным примером будет комбинация NFE и IIFE (Immediately-Invoked Function Expression), когда по-месту необходим только результат рекурсивной функции. Всего один раз:
let data = { f10: function fact(n) { return n > 1 ? n * fact(n - 1) : 1; }(10) }; data.f10; // 3628800
Такое себе? ну хорошо, вот реальная задачка: Есть некоторая строго-возрастающая функция f, действующая во множестве натуральных чисел. Найти крайнюю точку x, при которой значение функции не превышает заданного y. Примером такой функции могут быть f(x) = 3 * x + 5 или f(x) = 2^x + 11.
const find = (f, y) => function bs(a, b) { if (a + 1 === b) return a; let m = Math.ceil((a + b) / 2); return f(m) <= y ? bs(m, b) : bs(a, m); }(-1, y + 1); find(x => 3 * x + 5, 200); // 65 find(x => Math.pow(2, x) + 11, 1000); // 9
Вдаваться в математические детали не будем. Рассмотрим реализацию:
- Требуемая функция find имеет два наших параметра f, y и возвращает результат IIFE.
- Немедленно-вызываемая функция реализует бинарный поиск решения, первый вызов получает начальный диапазон.
- Сам поиск реализован через рекурсию, что использует имя NFE для последующих вызовов. Мы не декларируем функцию поиска и не создаем новую переменную.
Конечно, подобная задача может быть решена и через простой цикл с предусловием, но это слишком императивно для консольного кодинга.
Наконец пара слов про трассировку стека. У нас есть некоторая NFE-функция, в теле которой вылетело исключение:
const fe = function named() { throw new Error('Something went wrong'); };
Так как в выражении функция у нас именованная, мы увидим ее в стек-трейсе:
Uncaught Error: Something went wrong at named (<anonymous>:2:11)
Однако, начиная с ES-2015 многие анонимные выражения функций создают на самом деле функцию с именем, выводя его из контекста:
const unnamed = function() { throw new Error('Something went wrong'); };
Функция в правой части выражения ассоциируется с переменной в левой:
Uncaught Error: Something went wrong at unnamed (<anonymous>:2:7)
Но не всегда это возможно. Классический пример — инициализация скрипта внешней библиотеки через IIFE, как вариант:
/** * @license * @description */ ;(function() { // some awesome code }.call(this));
Или частый пример функции, возвращающей другую функцию:
const bind = (f, ctx) => function() { return f.apply(ctx, arguments); };
Тут нет переменной для вывода, поэтому в случае исключения мы увидим anonymous. NFE может немного помочь в разбирательствах.
Короткий вывод
NFE хорошо помогает писать лаконичный и неподдерживаемый код для быстрого прототипирования широкого спектра задач. Использовать их красоту в конечном коде следует пару раз подумав: JavaScript и без того перегружен интересными спецификами.
ссылка на оригинал статьи https://habr.com/post/427461/
Добавить комментарий