Функции высшего порядка и их использование в javascript

Spread the love

Перевод статьи Enmanuel DuránHigher order functions and some great applications in javascript

Что такое функции высшего порядка?

Функции высшего порядка (Higher order functions HOF) являются очень мощным понятием, в двух словах, можно сказать, что HOF – это функция, которая принимает в качестве аргумента другую функцию и/или возвращает функцию, поэтому, основываясь на этом, мы могли бы сказать, что они являются функциями более высокого порядка, потому что они действуют как «родитель» или «обертка» для других функций.

Пример

Допустим, у нас есть задача:

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

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

const counterToOneHundred = startingPoint => {
    const isOdd = startingPoint % 2;
    const interval = isOdd ? 5 : 10;

    for (let i = startingPoint; i < 100; i += interval) {
        console.log(`${i} of 100`);
    }
};

Отлично, все работает … верно? давайте посмотрим наш контрольный список:

  1.  Получает отправную точку startingPoint
  2.  Если исходной точкой является нечетное число, оно считается с интервалом 5
  3. Если отправной точкой является четное число, оно считается с интервалом в 10
  4.  Функция может выполниться сразу после предоставления отправной точки
  5.  Функция может выполниться на более позднем этапе программы

СТОП! мы пропустили одно требование, хотя мы почти сделали его, давайте попробуем проверить последний элемент нашего списка:

const startingPoint = 5; // starting point being any number

const counterToOneHundred = () => {
    const isOdd = startingPoint % 2;
    const interval = isOdd ? 5 : 10;

    for (let i = startingPoint; i < 100; i += interval) {
        console.log(`${i} of 100`);
    }
};

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

  1. Он может выполнить счетчик на более позднем этапе программы

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

  1. Чтобы определить startingPoint и выполнить счет независимо друг от друга, мы выставляем переменную за пределы реализации счетчика.
  2. Мы вычисляем интервал interval, когда выполняем функцию, но значение, необходимое для выполнения этого startingPoint, доступно гораздо раньше, а это значит, что мы могли бы рассчитать это заранее, чтобы избежать одновременного выполнения всех действий внутри функции. Мы могли бы достичь этого, переместив определения переменных isOdd и interval за пределы функции, но если бы мы это сделали, мы бы выставили еще больше переменных за пределами функции.
  3. Наличие открытых переменных увеличивает риск появления мутаций (случайных изменений) в нашем приложении и, следовательно, несоответствий.

Это не хорошо …

Функции высшего порядка идут нам на помощь

Меньше слов, больше кода:

const counterToOneHundred = startingPoint => {
    const isOdd = startingPoint % 2;
    const interval = isOdd ? 5 : 10;

    return () => {
        for (let i = startingPoint; i < 100; i += interval) {
            console.log(`${i} of 100`);
        }
    };
};

БУМ! вот и все, хорошего дня … шучу, теперь давайте посмотрим наш новый контрольный список и затем объясним нетривиальные моменты:

контрольный список

  1. Получает отправную точку startingPoint: Да. (Передано в качестве аргумента).
  2. Если исходной точкой является нечетное число, оно считается с интервалом 5: Да.
  3.  Если исходной точкой является четное число, оно считается с интервалом в 10: Да.
  4. Счетчик может выполниться сразу после предоставления startingPoint
  5. Счетчик может выполниться на более позднем этапе программы
  6. Счетчик хранит переменные инкапсулированными, изолированными от внешней области видимости.
  7.  Счетчик делает расчеты для interval, только когда это необходимо.

Пункт 4. «Счетчик может выполниться сразу после предоставления startingPoint»

Да. Когда мы выполняем нашу функцию наподобие counterToOneHundred(1)(), мы определяем переменные и возвращаем определение анонимной функции внутри в первом вызове функции, а затем выполняем внутреннюю функцию во втором вызове.

Пункт 5, «Счетчик может выполниться на более позднем этапе программы» и пункт 7. «Счетчик делает расчеты для interval, только когда это необходимо»

Да. Мы можем сохранить возвращение первого вызова функции, а затем вызвать внутреннюю функцию при необходимости:

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

const counter = counterToOneHundred(1);

Затем мы запускаем счетчик на более позднем этапе, когда это необходимо

counter();

Пункт 6 «Счетчик хранит переменные инкапсулированными, изолированными от внешней области видимости.»

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

Таким образом, используя HOF, мы смогли

  • Инкапсулировать наши данные.
  • Повысить гибкость нашей реализации.
  • Оптимизировать код и порядок выполнения процессов.

Более реалистичный пример

Теперь достаточно счетчиков, давайте использовать HOF для лучшего примера, более реалистичного. Представьте, что нам нужно создать три кнопки общего доступа, чтобы опубликовать нашу текущую страницу в Twitter, Facebook или Linkedin, эти кнопки откроют всплывающее окно при нажатии на их в зависимости от типа кнопки.

Реализация этого может выглядеть примерно так:

const share = () => {
 
    const pageUrl = 'https://enmascript.com';
    const pageTitle = 'A place to share about web development and science';
    const networks = {
        twitter: `https://twitter.com/share?url=${pageUrl}&text=${pageTitle}`,
        facebook: `https://www.facebook.com/sharer/sharer.php?u=${pageUrl}`,
        linkedIn: `https://www.linkedin.com/shareArticle?mini=true&url=${pageUrl}`
    };

    /**
     * получаем сеть и возвращаем функцию открытия окна
     */
    return network => event => {
        event.preventDefault();

        /* если указана не правильная сеть выходим */
        if (!(network in networks)) {
            return false;
        }

        /* открытие окна в зависимости от выбранной сети */
        const networkWindow = window.open(
            networks[network],
            'network-popup',
            'height=350, width=600'
        );

        /* Применение фокуса к всплывающему окну после его открытия. */
        if (networkWindow.focus) {
            networkWindow.focus();
        }
    };
};

И возможное использование этого (скажем, на React) будет выглядеть примерно так:

/* We setup the data once */
const shareOn = share();

/* We validate each network and open the popup on click */
<div onClick={shareOn('twitter')}><Twitter /></div>
<div onClick={shareOn('facebook')}><Facebook /></div>
<div onClick={shareOn('linkedIn')}><LinkedIn /></div>

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

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

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

Ловля ошибок

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

Реализация

function errorCatcher(cb) {
    try {
        cb();
    } catch (error) {
        console.log('Ups, Looks like something went wrong!');
    }
}

Использование

function sayHi() {
    const person = { name: 'Daniel' };

    console.log(`Hi, ${person.name} ${person.career.name}`);
}

errorCatcher(sayHi);

Throttler

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

Реализация

function throttle(throttledFn, delayTime) {
    let lastCallTime = 0;

    return (...args) => {
        const currentCallTime = new Date().getTime();

        if (currentCallTime - lastCallTime < delayTime) return;

        lastCallTime = currentCallTime;
        throttledFn(...args);
    };
}

Использование

function logger() {
    console.log(`I'm executed every 200ms when actively scrolling`);
}

window.addEventListener('scroll', throttle(logger, 200));

Простая проверка производительности для функции

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

Реализация

function performance(fn) {
    console.time('fn');
    fn();
    console.timeEnd('fn');
}

Использование

function loop() {
    for (i = 0; i < 1000; i++) {
        console.log('executing loop to 1000');
    }
}

performance(loop);

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

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

Хотите оставить комментарий? Сделай это в twitter

Была ли вам полезна эта статья?
[0 / 0]

Spread the love
Подписаться
Уведомление о
guest
0 Комментарий
Inline Feedbacks
View all comments