«Вопросы на собеседовании, серия 6», чтобы понять каррирование функций.

опрос
«Вопросы на собеседовании, серия 6», чтобы понять каррирование функций.

предисловие

Это 6-я часть серии вопросов для фронтенд-интервью, возможно, вы пропустили предыдущую, ее можно найти здесь:

Недавно мой друг Т. готовился к собеседованию, застрял на вопросе по программированию и попросил меня о помощи. Оригинальное название выглядит следующим образом:

// 写一个 sum 方法,当使用下面的语法调用时,能正常工作
console.log(sum(2, 3)); // Outputs 5
console.log(sum(2)(3)); // Outputs 5

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

  • Если мы передаем два аргумента, мы просто добавляем их и возвращаемся.
  • В противном случае мы предполагаем, что она была вызвана как sum(2)(3), поэтому мы возвращаем анонимную функцию, которая принимает аргументы, переданные в sum() (в данном случае 2), и аргументы, переданные в параметр анонимной функции (3 в данном случае). кейс).

Таким образом, функцию суммы можно записать так:

function sum (x) {
    if (arguments.length == 2) {
        return arguments[0] + arguments[1];
    }
    
    return function(y) {
        return x + y;
    }
}

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

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

Что такое карри

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

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

Определение каррирования немного сложно понять. Для лучшего понимания рассмотрим следующий пример:

function sum (a, b, c) {
    console.log(a + b + c);
}
sum(1, 2, 3); // 6

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

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

Если вас не волнует конкретная реализация в функции, вызов можно записать так:sum(1, 2)(3);или вот такsum(1, 2)(10);. То есть сначала получить результаты работы первых двух параметров, а затем добавить их к третьему параметру.

На самом деле это простое применение каррирования функций.

Реализация каррирования

sum(1, 2)(3);Такой способ письма не распространен. Разбери его,sum(1, 2)Возврат по-прежнему должен быть функцией, потому что все еще есть(3)нужно выполнить.

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

В Интернете есть несколько различных реализаций каррирования, самый простой способ написать это так:

function curry (fn, currArgs) {
    return function() {
        let args = [].slice.call(arguments);

        // 首次调用时,若未提供最后一个参数currArgs,则不用进行args的拼接
        if (currArgs !== undefined) {
            args = args.concat(currArgs);
        }

        // 递归调用
        if (args.length < fn.length) {
            return curry(fn, args);
        }

        // 递归出口
        return fn.apply(null, args);
    }
}

Проанализируем написание функции карри:

Во-первых, у него 2 параметра, fn относится к функции-обработчику источника в начале этой статьи.sum. Currargs - это список аргументов, передаваемых при вызове карри, например(1, 2)(3)Такой.

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

следующийlet args = [].slice.call(arguments);, что означает массив аргументов. arguments представляет собой структуру, подобную массиву, на самом деле это не массив, поэтому вы не можете использовать методы массива. С помощью метода call мы можем с удовольствием использовать нативные методы массивов для аргументов. в этомВ «Галантерее» подробно рассказывается о различии и использовании терминов «вызвать, применить и связать»., есть более подробное введение в использование call.

currArgs !== undefinedРешение состоит в том, чтобы решить проблему объединения параметров во время рекурсивных вызовов.

Наконец, оцените, равно ли количество args количеству параметров fn (то есть sum ). Если они равны, все параметры могут быть переданы в fn для вывода, в противном случае рекурсивный вызов продолжается до тех пор, пока они не будут равны .

есть тест:

function sum(a, b, c) {
    console.log(a + b + c);
}

const fn = curry(sum);

fn(1, 2, 3); // 6
fn(1, 2)(3); // 6
fn(1)(2, 3); // 6
fn(1)(2)(3); // 6

Может вывести 6, получите!

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

Теперь, когда мы разобрались с реализацией каррирования, давайте взглянем на его практическое применение. Цель каррирования — уменьшить избыточность кода и повысить его читаемость. Рассмотрим следующий пример:

const persons = [
    { name: 'kevin', age: 4 },
    { name: 'bob', age: 5 }
];

// 这里的 curry 函数,之前已实现
const getProp = curry(function (obj, index) {
    const args = [].slice.call(arguments);
    return obj[args[args.length - 1]];
});

const ages = persons.map(getProp('age')); // [4, 5]
const names = persons.map(getProp('name')); // ['kevin', 'bob']

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

должны знать о том,const names = persons.map(getProp('name'));При выполнении этого оператора есть только один параметр getProp.name, а при определении метода getProp в curry передаются 2 параметра,objа такжеindex(здесь должны быть записаны 2 или более параметра).

Зачем так писать? Дело в томargumentsнеявный параметр.

const getProp = curry(function (obj, index) {
    console.log(arguments);
    // 会输出4个类数组,取其中一个来看
    // {
    //     0: {name: "kevin", age: 4},
    //     1: 0,
    //     2: [
    //         {name: "kevin", age: 4},
    //         {name: "bob", age: 5}
    //     ],
    //     3: "age"
    // }
});

MAP — это родной метод Array, который выглядит следующим образом:

var new_array = arr.map(function callback(currentValue[, index[, array]]) {
    // Return element for new_array
}[, thisArg]);

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

const getProp = curry(function (obj, index) {
    const args = [].slice.call(arguments);
    return obj[args[args.length - 1]];
});

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

const getProp = curry(function (key, obj) {
    return obj[key];
});

const ages = persons.map(item => {
    return getProp(item)('age');
});
const names = persons.map(item => {
    return getProp(item)('name');
});

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

function memoizeFunction(func) {
    const cache = {};
    return function() {
        let key = arguments[0];
        if (cache[key]) {
            return cache[key];
        } else {
            const val = func.apply(null, arguments);
            cache[key] = val;
            return val;
        }
    };
}

const fibonacci = memoizeFunction(function(n) {
    return (n === 0 || n === 1) ? n : fibonacci(n - 1) + fibonacci(n - 2);
});

console.log(fibonacci(100)); // 输出354224848179262000000
console.log(fibonacci(100)); // 输出354224848179262000000

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

Суммировать

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

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

PS: Добро пожаловать, чтобы обратить внимание на мою общедоступную учетную запись «Super Brother Front-end Small Stack», чтобы обменяться идеями и технологиями.