JS对象类型-函数进阶篇-函数柯里化
函数柯里化currying的概念最早由俄国数学家Moses Schönfinkel发明,而后由著名的数理逻辑学家Haskell Curry将其丰富和发展,currying由此得名。
定义
currying又称部分求值。柯里化函数首先会接受一些参数,参数接收之后不会立即求值,而是继续返回一个新的函数,之前传入的参数会在新函数形成的闭包中保存起来,等到函数真正需要求值的时候,之前传入的所有参数会被一次性用于求值。
概念不好理解,举例说明
【计算每月的开销】有一项任务,要求计算出每月的开销。最简单的方法肯定是记录下每天的开销,然后月末的时候加到一起就行了,于是可以这样写:
var totalMoney = 0;
var cost = function(money) {
totalMoney += money
}
cost(100) // 第1天消费
cost(200) // 第2天消费
// ...
cost(100) // 第30天消费
console.log(totalMoney) // 当月消费金额
上面的函数计算了每天的开销,但是实际我只需要知道月末的消费就好了,其他时间的消费总和我并不关心,于是可以这样改写:
var cost = (function(){
var args = [];
return function() {
if(arguments.length === 0) { // 如果没有参数,求和并返回结果
var totalMoney = 0;
for(var i = 0, len = args.length; i < len; i++) {
totalMoney += args[i]
}
return totalMoney
} else { // 如果有参数,把参数添加到数组
[].push.apply(args, arguments)
}
}
})()
cost(100) // 不求值
cost(200) // 不求值
cost(100) // 不求值
cost() // 400
举这个例子只是便于理解概念,并不能体现柯里化函数的强大之处,它真正的用处是函数式编程。
通用函数
把上面的例子改写成一个通用的柯里化函数,把一个将要被柯里化的函数作为参数。
var currying = function (fn) {
var args = [];
return function () {
if (arguments.length === 0) { // 如果没有传参就进行求值
return fn.apply(this, args);
} else {
// 把fn的参数展开,添加到数组里面
[].push.apply(args, arguments);
}
}
};
var totalCost = (function () {
var totalMoney = 0;
return function () {
for (var i = 0, l = arguments.length; i < l; i++) {
totalMoney += arguments[i];
}
return totalMoney;
}
})();
var cost = currying(totalCost); // 转化成 currying 函数
cost(100); // 未真正求值
cost(200); // 未真正求值
cost(100); // 未真正求值
console.log(cost()); // 400
可传参的函数
柯里化函数不仅可以接受一个将要被柯里化的函数作为参数,也可以接受一些参数。
var currying = function (fn) {
var args = [].slice.call(arguments, 1); // 获取除fn之外的其他参数
return function () {
if (arguments.length === 0) {
return fn.apply(this, args);
} else {
[].push.apply(args, arguments);
}
}
};
var totalCost = (function () {
var totalMoney = 0;
return function () {
for (var i = 0, l = arguments.length; i < l; i++) {
totalMoney += arguments[i];
}
return totalMoney;
}
})();
var cost = currying(totalCost, 100, 200);
cost(100);
cost(200);
cost(100);
console.log(cost()); // 700
求值柯里化
柯里化函数传参的同时,可以进行求值。
var currying = function (fn) {
var args = [].slice.call(arguments, 1);// 获取除fn外的其他参数
return function () {
// 把fn里面的参数转换为数组
var innerArgs = [].slice.call(arguments)
// 合并参数
var finalArgs = args.concat(innerArgs);
// 把参数传给fn函数
return fn.apply(null, finalArgs);
}
};
var totalCost = (function () {
var totalMoney = 0;
return function () {
for (var i = 0, l = arguments.length; i < l; i++) {
totalMoney += arguments[i];
}
return totalMoney;
}
})();
var cost = currying(totalCost, 100, 200);
console.log(cost(100)); // 100 + 200 + 100 = 400
console.log(cost(100,200)); // 400 + 100 + 200 + 100 + 200 = 1000
反柯里化
介绍call()和apply()方法时说过,它们都可以改变函数中this的指向,这也是为什么数组的方法可以用到对象上。反柯里化就是为了把泛化的this提取出来,扩大方法的适用范围,使本来只能用于特定对象的方法扩展到更多的对象。
下面是两种实现uncurrying的方法:
Function.prototype.uncurrying = function(){
var _this = this;
return function(){
return Function.prototype.call.apply(_this, arguments)
}
}
Function.prototype.uncurrying = function(){
var _this = this;
return function(){
var obj = Array.prototype.shift.call(arguments)
return _this.apply(obj, arguments)
}
}
这两种方式的原理是一样的,都是把this.method(arg1, arg2)转换成method(this, arg1, arg2)
【示例】让对象拥有push()方法
var push = [].push.uncurrying(), obj = {}, arr = [];
push(obj, 'hello', 'world')
push(arr, 'hello', 'world')
console.log(obj, arr) // {0: 'hello', 1: 'world', length: 2} ['hello', 'world']
【示例】让数组拥有toUpperCase方法
var toUpperCase = String.prototype.toUpperCase.uncurrying(), arr = ['hello','world'];
console.log(arr.map(toUpperCase)) // ['HELLO', 'World']
胖胖熊笔记,笔记已迁移
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 解答了困扰我五年的技术问题
· 为什么说在企业级应用开发中,后端往往是效率杀手?
· 用 C# 插值字符串处理器写一个 sscanf
· Java 中堆内存和栈内存上的数据分布和特点
· 开发中对象命名的一点思考
· 为什么说在企业级应用开发中,后端往往是效率杀手?
· DeepSeek 解答了困扰我五年的技术问题。时代确实变了!
· 本地部署DeepSeek后,没有好看的交互界面怎么行!
· 趁着过年的时候手搓了一个低代码框架
· 推荐一个DeepSeek 大模型的免费 API 项目!兼容OpenAI接口!