js - 理解函数柯里化

本文详细介绍了柯里化(Currying)的概念,即如何将一个多参数的函数转换为一系列单一参数的函数调用。通过示例展示了如何使用柯里化实现参数复用,简化代码。同时,提供了四个不同版本的JavaScript柯里化实现,从基础到进阶,逐步揭示柯里化的原理。最后,探讨了函数式编程思想在柯里化中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

怎么理解柯里化

柯里化的意思就是将一个多元函数,转换成一个依次调用的单元函数。

curry 的这种用途可以理解为:参数复用。本质上是降低通用性,提高适用性。

f(a,b,c)f(a)(b)(c)

var add = function(x) {
  return function(y) {
    return x + y;
  }; 
};
const increment = add(1);

increment(10); // 11
var { curry } = require('lodash')

var abc = function(a, b, c) {
  return [a, b, c];
};
 
var curried = curry(abc);
 
console.log(curried(1)(2)(3));
// => [1, 2, 3]
 
var person = [{name: 'kevin', age: 12 }, {name: 'daisy', age: 13}]

var prop = curry(function (key, obj) {
  return obj[key]
});

// 普通方式
var name = person.map(function (item) {
    return item.name;
})

// 柯里化方式
var name = person.map(prop('name'))
console.log(name) // [ 'kevin', 'daisy' ]

var age = person.map(prop('age'))
console.log(age) // [ 12, 13 ]


image.png

上面代码,我们将返回对象属性的部分通过函数编程实现。

var prop = curry(function (key, obj) {
  return obj[key]
});

curry函数将传入的function内的两个参数。变成两个使用一个参数的函数执行。

function(key, obj) 

// 根据柯里化转换转换成两个函数,并且第一个参数调用之后返回的还是一个函数,参数为原函数的第二个参数。

props(key) => function(obj)

精髓理解:用闭包把参数保存起来,当参数的数量足够执行函数了,就开始执行函数

手写柯里化

初始版本

var abc = function(a, b, c) {
  return [a, b, c];
};

function customerCurry(fn) {
  const fnArgsCount = fn.length;  	// fn函数的形参个数
  let curryArgs = [];			// 柯里化函数已经传入的实惨个数
  
  function wrapperFunction () {
    curryArgs = curryArgs.concat([...arguments])
      
    if (curryArgs.length < fnArgsCount) {
      return wrapperFunction;
     }
      
     return fn.apply(this, curryArgs)
   }
  
   return wrapperFunction;
}

var incre = customerCurry(abc)
incre(1)(2)(3)  // 正常返回 [1, 2, 3]

incre() 				// 返回 [1, 2, 3]


上面的代码可以正常返回,但是还是存在问题。


只能执行一次,incre不能多次执行。这里因为上面书写的方式将参数统一记录下来了。customerCurry参数是最外层必包在维护。所以在incre(1)(2)(3) 执行完之后。内部参数个数已经达到三个


第二版本

var abc = function(a, b, c) {
  return [a, b, c];
};

function customerCurry(fn) {
  	const fnArgsCount = fn.length;  	// fn函数的形参个数
  	let curryArgs = [];								// 柯里化函数已经传入的实惨个数
  
		if (fnArgsCount <= 1) {
    	throw new Error('函数至少传入两个参数');
    }
  
  	function wrapperFunction () {
    	curryArgs = [...arguments];
      
      if (curryArgs.length < fnArgsCount) {
      	return eval(`wrapperFunction.bind(this, ${curryArgs.reduce(function(x, y) {return x + ',' + JSON.stringify(y);})} )`);
      }
      
      return fn.apply(this, curryArgs)
    }
  
  	return wrapperFunction;
}

var incre = customerCurry(abc)
incre(1)(2)(3)  // 正常返回 [1, 2, 3]


当然这种还是有缺陷,因为对于参数的传递,这里处理不是很好,使用的是json的方法。


第三版本

var abc = function(a, b, c) {
  return [a, b, c];
};

function customerCurry(fn) {
  	const fnArgsCount = fn.length;  	// fn函数的形参个数
  	const curryWrapperArgs = [...arguments].slice(1);
  
  	function wrapperFunction () {
    	let curryArgs = [fn, ...curryWrapperArgs, ...arguments];
      
      if (curryArgs.length < fnArgsCount + 1) {
      	return customerCurry.apply(this, curryArgs);
      }
      
      return fn.apply(this, curryArgs.slice(1))
    }
  
  	return wrapperFunction;
}

var incre = customerCurry(abc)
incre(1)(2)(3)  // 正常返回 [1, 2, 3]



第四版本

这样写起来没什么问题,但是既然学到了柯里化,函数式编程的思想去书写一下。

例子来源:https://github.com/mqyqingfeng/Blog/issues/42

// 第二版
function sub_curry(fn) {
    var args = [].slice.call(arguments, 1);
    return function() {
        return fn.apply(this, args.concat([].slice.call(arguments)));
    };
}

function curry(fn, length) {

    length = length || fn.length;

    var slice = Array.prototype.slice;

    return function() {
        if (arguments.length < length) {
            var combined = [fn].concat(slice.call(arguments));
            return curry(sub_curry.apply(this, combined), length - arguments.length);
        } else {
            return fn.apply(this, arguments);
        }
    };
}


执行顺序:
image.png

换一个方向:
image.png
image.png

现在我们用函数式编程的思想来理解一下。

image.pngimage.png

第五版本:高颜值写法
var curry = fn =>
    judge = (...args) =>
        args.length === fn.length
            ? fn(...args)
            : (arg) => judge(...args, arg)

参照

  • https://github.com/mqyqingfeng/Blog/issues/42
  • https://juejin.cn/post/6844903936378273799#heading-14
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值