В связи с недавней отставкой по поиску работы, я давно не писал статьи, поэтому пришлось убрать статью, написанную в 2017 году, и расхвалить ее ххх.
концепция
Подводя итог своими словами, каррирование функции означает, что вы можете передать много параметров в функцию карри за один раз, или вы можете передать ее несколько раз.Функция карри будет возвращать функцию каждый раз для обработки оставшихся параметров, пока она не вернет , окончательный результат.
пример
Вот несколько примеров для иллюстрации:
Каррированная функция суммирования
// 普通方式
var add1 = function(a, b, c){
return a + b + c;
}
// 柯里化
var add2 = function(a) {
return function(b) {
return function(c) {
return a + b + c;
}
}
}
Каждый раз, когда передается параметр, будет возвращена новая функция, и она будет выполняться до тех пор, пока значение a+b+c не будет возвращено в последний раз. Но есть еще проблема с этой реализацией.Здесь всего три параметра.Если в один прекрасный день продакт-менеджер скажет нам,что надо менять 100 раз? Мы просто перепишем это 100 раз? Это явно не соответствует принципу открыт-закрыт, поэтому нам нужно внести модификацию в функцию.
var add = function() {
var _args = [];
return function() {
if(arguments.length === 0) {
return _args.reduce(function(a, b) {
return a + b;
})
}
[].push.apply(_args, arguments);
return arguments.callee;
}
}
var sum = add();
sum(100, 200)(300);
sum(400);
sum(); // 1000
Мы решаем, запускается ли функция, оценивая, передаются ли параметры в следующий раз.Если параметры продолжают передаваться, то мы продолжаем сохранять параметры и запускаем их все одновременно при запуске, чтобы мы изначально завершили каррирующая функция.
Универсальная функция каррирования
Вот просто функция суммирования, а если заменить ее произведением? Нужно ли нам писать все заново? Присмотритесь к нашей функции добавления: если мы заменим код в if кодом выполнения функции, может ли она стать общей функцией?
var curry = function(fn) {
var _args = [];
return function() {
if(arguments.length === 0) {
return fn.apply(fn, _args);
}
[].push.apply(_args, arguments);
return arguments.callee;
}
}
var multi = function() {
return [].reduce.call(arguments, function(a, b) {
return a + b;
})
}
var add = curry(multi);
add(100, 200, 300)(400);
add(1000);
add(); // 2000
В предыдущем методе мы расширили его, чтобы реализовать более общую функцию каррирования. Может быть, вы хотите спросить, а что, если я не хочу каждый раз использовать это уродливое окончание в скобках?
var curry = function(fn) {
var len = fn.length,
args = [];
return function() {
Array.prototype.push.apply(args, arguments)
var argsLen = args.length;
if(argsLen < len) {
return arguments.callee;
}
return fn.apply(fn, args);
}
}
var add = function(a, b, c) {
return a + b + c;
}
var adder = curry(add)
adder(1)(2)(3)
Здесь решение принимается в соответствии с количеством параметров функции fn.Окончательный результат работы функции fn не будет возвращен до тех пор, пока количество переданных параметров не будет равно количеству параметров, требуемых функцией fn. Принцип тот же, что и у описанного выше метода, но эти два метода слишком сильно зависят от количества параметров. Я также видел другой рекурсивный метод реализации других в Цзяньшу.На самом деле идея реализации похожа на мою.
// 简单实现,参数只能从右到左传递
function createCurry(func, args) {
var arity = func.length;
var args = args || [];
return function() {
var _args = [].slice.call(arguments);
[].push.apply(_args, args);
// 如果参数个数小于最初的func.length,则递归调用,继续收集参数
if (_args.length < arity) {
return createCurry.call(this, func, _args);
}
// 参数收集完毕,则执行func
return func.apply(this, _args);
}
}
Вот расчет количества параметров, а если нужно неограниченное количество параметров? Например, следующий сценарий.
add(1)(2)(3)(2);
add(1, 2, 3, 4, 5);
Здесь есть знание, что есть функция неявного преобразования, задействующая два метода toString и valueOf, при прямом вычислении функции функция сначала преобразуется в строку, а потом снова участвует в вычислении, использовать ее можно двумя способами. изменить функцию.
var num = function() {
}
num.toString = num.valueOf = function() {
return 10;
}
var anonymousNum = (function() { // 10
return num;
}())
После модификации окончательная версия нашей функции выглядит так.
var curry = function(fn) {
var func = function() {
var _args = [].slice.call(arguments, 0);
var func1 = function() {
[].push.apply(_args, arguments)
return func1;
}
func1.toString = func1.valueOf = function() {
return fn.apply(fn, _args);
}
return func1;
}
return func;
}
var add = function() {
return [].reduce.call(arguments, function(a, b) {
return a + b;
})
}
var adder = curry(add)
adder(1)(2)(3)
Так какой смысл придираться после того, как мы так много сказали?
Предварительная загрузка
Во многих сценариях некоторые параметры функции, которые нам нужны, скорее всего, будут одинаковыми. В настоящее время нерационально писать снова и снова. Мы загружаем некоторые параметры заранее, а затем передаем оставшиеся параметры. Характеристики замыканий в основном используются здесь., первоначальный объем может быть сохранен за счет закрытия.
var match = curry(function(what, str) {
return str.match(what);
});
match(/\s+/g, "hello world");
// [ ' ' ]
match(/\s+/g)("hello world");
// [ ' ' ]
var hasSpaces = match(/\s+/g);
// function(x) { return x.match(/\s+/g) }
hasSpaces("hello world");
// [ ' ' ]
hasSpaces("spaceless");
// null
В приведенном выше примере функция hasSpaces используется для сохранения правил регулярных выражений, что позволяет эффективно повторно использовать параметры.
Создать функцию динамически
На самом деле это идея ленивой функции, Мы можем заранее выполнить условие суждения и сохранить его в допустимой области действия через замыкание, чтобы увидеть общий сценарий, в котором мы обычно пишем код.
var addEvent = function(el, type, fn, capture) {
if (window.addEventListener) {
el.addEventListener(type, function(e) {
fn.call(el, e);
}, capture);
} else if (window.attachEvent) {
el.attachEvent("on" + type, function(e) {
fn.call(el, e);
});
}
};
В этом примере каждый раз, когда мы вызываем addEvent, мы будем повторно выполнять оператор if для оценки, но на самом деле условия браузера вряд ли изменятся.Вы судите один раз и судите N раз, результаты одинаковы, поэтому это можно судить Условия загружаются рано.
var addEventHandler = function(){
if (window.addEventListener) {
return function(el, sType, fn, capture) {
el.addEventListener(sType, function(e) {
fn.call(el, e);
}, (capture));
};
} else if (window.attachEvent) {
return function(el, sType, fn, capture) {
el.attachEvent("on" + sType, function(e) {
fn.call(el, e);
});
};
}
}
var addEvent = addEventHandler();
addEvent(document.body, "click", function() {}, false);
addEvent(document.getElementById("test"), "click", function() {}, false);
Но у этого все же есть недостаток, потому что мы не можем судить, используется ли этот метод в программе, но мы все равно должны определить addEvent в начале файла, что фактически тратит ресурсы.Вот лучшее решение.
var addEvent = function(el, sType, fn, capture){
if (window.addEventListener) {
addEvent = function(el, sType, fn, capture) {
el.addEventListener(sType, function(e) {
fn.call(el, e);
}, (capture));
};
} else if (window.attachEvent) {
addEvent = function(el, sType, fn, capture) {
el.attachEvent("on" + sType, function(e) {
fn.call(el, e);
});
};
}
}
Переназначьте его в функции addEvent, что не только решит проблему оценки каждого запуска, но и решит проблему расточительного выполнения в верхней части области видимости.
React
По дороге домой я думал о том, можно ли распространить каррирование функций на большее количество сценариев Я хочу попробовать заменить функцию компонентом реагирования? Я подумал о компонентах более высокого порядка и соединении редукса, которые действительно являются воплощением применения идеи каррирования для реагирования. Давайте подумаем, что, если функции в приведенном выше примере заменить компонентами, а параметры заменить функциями более высокого порядка?
var curry = function(fn) {
var func = function() {
var _args = [].slice.call(arguments, 0);
var func1 = function() {
[].push.apply(_args, arguments)
return func1;
}
func1.toString = func1.valueOf = function() {
return fn.apply(fn, _args);
}
return func1;
}
return func;
}
var hoc = function(WrappedComponent) {
return function() {
var len = arguments.length;
var NewComponent = WrappedComponent;
for (var i = 0; i < len; i++) {
NewComponent = arguments[i](NewComponent)
}
return NewComponent;
}
}
var MyComponent = hoc(PageList);
curry(MyComponent)(addStyle)(addLoading)
Этот пример расширяет исходный компонент PageList, добавляя стили и функции загрузки в PageList. Если вы хотите добавить другие функции, вы можете продолжить расширять его (обратите внимание, что addStyle и addLoading являются высокоуровневыми компонентами), но метод записи действительно очень Ой, это совсем не круто, мы можем использовать метод compose, который уже есть в библиотеках underscore и loadsh.
var enhance = compose(addLoading, addStyle);
enhance(MyComponent)
Суммировать
На самом деле ядром каррирования является гибкое использование замыканий функций.После глубокого понимания замыканий и области видимости можно написать много гибких и оригинальных методов.