предисловие
Это 6-я часть серии вопросов для фронтенд-интервью, возможно, вы пропустили предыдущую, ее можно найти здесь:
- Как использовать стрелочные функции в ES6
- Принцип и использование этого
- Разница и реальная борьба между псевдоклассами и псевдоэлементами
- Как реализовать макет Святого Грааля?
- Сегодняшние главные вопросы интервью и анализ идей
Недавно мой друг Т. готовился к собеседованию, застрял на вопросе по программированию и попросил меня о помощи. Оригинальное название выглядит следующим образом:
// 写一个 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», чтобы обменяться идеями и технологиями.