简介
柯里化,英语:Currying。是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。
-
是一个部分配置多参数函数的过程 -
每一步都返回一个接受单个参数的部分配置好的函数。
可以理解成,将一个接受多个参数的函数 变为 接受一个参数返回一个函数的固定形式,这样便于再次调用,例如f(1)(2)。其数学表达式为:
z = f(x, y) 转换成 z = f(x)(y) 的形式
// 普通的add函数
function add(x, y) {
return x + y
}
// Currying后
function curryingAdd(x) {
return function (y) {
return x + y
}
}
add(1,2) // 3
curryingAdd(1)(2) // 3
如果用链式调用,可以这么写
// 链式调用
var d = 1;
d.add(2).add(3).add(4) //输出10
Number.prototype.add = function(x){
return this + x;
}
这样看来,柯里化和链式调用是有异曲同工之妙的。
用es6的箭头函数语法来理解,可能更为直观一些。
let add = x => y => x + y
let add2 = add(2)
具体调用过程如下:
前 n – 1 次调用,其实是为了将参数传递进去,并没有调用最内层函数体,最后一次调用才会调用最内层函数体,并返回最内层函数体的返回值。
所以连续箭头函数可以看做是多次柯里化函数的 es6 写法。
n 个连续箭头组成的函数实际上就是柯里化了 n – 1次,其中涉及到一些作用域链的内容,可以到参考资料中查阅了解一下或者自行搜索。
let test = a => b => c => {console.log();}
a()()()
它突出了一种思想——降低适用范围,提高适用性。
题目
实现 add(1)(2)(3)
(一)粗暴版
function add (a) {
return function (b) {
return function (c) {
return a + b + c;
}
}
}
console.log(add(1)(2)(3)); // 6
(二)柯里化解决方案
-
参数长度固定
const curry = (fn) => (
judge = (...args) => args.length === fn.length ?
fn(...args):
(...arg) => judge(...args, ...arg)
);
const add = (a, b, c) => a + b + c;
const curryAdd = curry(add);
console.log(curryAdd(1)(2)(3)); // 6
console.log(curryAdd(1, 2)(3)); // 6
console.log(curryAdd(1)(2, 3)); // 6
实现add(1, 2, 3)(4)(5)(6)
-
参数长度不固定
function add (...args) {
//求和
return args.reduce((a, b) => a + b)
}
function currying (fn) {
let args = []
return function temp(...newArgs) {
if (newArgs.length) {
args = [
...args,
...newArgs
]
return temp
} else {
let val = fn.apply(this, args)
//保证再次调用时清空
args = []
return val
}
}
}
let addCurry = currying(add)
console.log(addCurry(1)(2)(3)(4, 5)(5)()) //20
console.log(addCurry(1)(2)(3, 4, 5)()) //15
console.log(addCurry(1)(2, 3, 4, 5)()) //15
另一种写法
function add() {
// 第一次执行时,定义一个数组专门用来存储所有的参数
var _args = Array.prototype.slice.call(arguments);
// 在内部声明一个函数,利用闭包的特性保存_args并收集所有的参数值
var _adder = function() {
_args.push(...arguments);
return _adder;
};
// 利用toString隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回
_adder.toString = function () {
return _args.reduce(function (a, b) {
return a + b;
});
}
return _adder;
}
add(1)(2)(3) // 6
add(1, 2, 3)(4) // 10
add(1)(2)(3)(4)(5) // 15
add(2, 6)(1) // 9
应用
lodash.curry
_.curry(func, [arity=func.length])
创建一个函数,该函数接收 func
的参数,要么调用func
返回的结果,如果 func
所需参数已经提供,则直接返回 func
所执行的结果。或返回一个函数,接受余下的func
参数的函数,可以使用 func.length
强制需要累积的参数个数。
_.curry.placeholder
值,默认是以 _
作为附加部分参数的占位符。
参数
-
func
(Function): 用来柯里化(curry)的函数。 -
[arity=func.length]
(number): 需要提供给func
的参数数量。
返回
(Function): 返回新的柯里化(curry)函数。
例子
var abc = function(a, b, c) {
return [a, b, c];
};
var curried = _.curry(abc);
curried(1)(2)(3);
// => [1, 2, 3]
curried(1, 2)(3);
// => [1, 2, 3]
curried(1, 2, 3);
// => [1, 2, 3]
// Curried with placeholders.
curried(1)(_, 3)(2);
// => [1, 2, 3]
减少重复传递不变的部分参数
function simpleURL(protocol, domain, path) {
return protocol + "://" + domain + "/" + path;
}
上面这个函数是将三个参数生成一个完成的url.调用如下:
let myurl = simpleURL('http', 'mysite', 'home.html');
let myurl2 = simpleURL('http', 'mysite', 'aboutme.html');
然后你会发现,前两个参数保持不变,但每次调用都需要传递。所以可以对其优化,仅传递最后一个变化的参数。
function simpleURL(path) {
return "http://mysite/" + path;
}
这种就属于硬写了,是绝对不允许的。
使用lodash.curry库函数使函数柯里化
// 避免每次调用重复传参
let myURL1 = _.curry(simpleURL)('https', 'mysite');
let res1 = myURL1('home.html'); //
console.log(res1);//https://mysite/home.html
let myURL2 = _.curry(simpleURL)('http', 'mysite');
let res2 = myURL2('aboutme.html'); //
console.log(res2);//http://mysite/aboutme.html
将柯里化后的callback参数传递给map, filter等函数。
比如我们有这样一段数据:
let persons = [{name: 'kevin', age: 11}, {name: 'daisy', age: 24}]
如果我们要获取所有的 name 值,我们一般会这样实现:
let names = persons.map(function (item) {
return item.name;
});
可以利用柯里化改写成如下:
let getProp = _.curry(function (key, obj) {
return obj[key]
});
let names = persons.map(getProp('name'))
getProp 函数编写一次后可以多次使用。
let persons = [{name: 'kevin', age: 11}, {name: 'daisy', age: 24}]
let getProp = _.curry(function (key, obj) {
return obj[key]
});
let names2 = persons.map(getProp('name'))
console.log(names2); //['kevin', 'daisy']
let ages2 = persons.map(getProp('age'))
console.log(ages2); //[11,24]
在这个场景中,将callback柯里化之后,就能实现callback的复用了,而且非常灵活,这样不需要每次map计算都新写一个匿名函数,并在回调里加上特有的逻辑,导致其无法重用。
固定部分配置项
思路和减少重复传递不变的部分参数大同小异。
例如,有一个函数加载配置,前面几项固定了,而后面部分项不固定
// 举个例子 一个爬虫
let load_config_spider = spider.load("target.com", "get", "id=1", "name=xxx")
load_config_spider("sex=m")("page=15")
参考资料
[详解JS函数柯里化]https://www.jianshu.com/p/2975c25e4d71
[JS 执行环境、作用域链、活动对象]https://segmentfault.com/a/1190000015782315
《[简述几个非常有用的柯里化函数使用场景]https://segmentfault.com/a/1190000015281061)
[《lodash源码学习curry,curryRight》]https://www.cnblogs.com/wandiao/p/7188588.html
原文始发于微信公众号(豆子前端):[js基础]柯里化究竟为何物?
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/56771.html