Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CodeShredded - 函数柯里化 #17

Open
jtwang7 opened this issue Oct 6, 2021 · 0 comments
Open

CodeShredded - 函数柯里化 #17

jtwang7 opened this issue Oct 6, 2021 · 0 comments

Comments

@jtwang7
Copy link
Owner

jtwang7 commented Oct 6, 2021

参考文章:

什么是函数柯里化?

在数学和计算机科学中,柯里化是一种将使用多个参数的一个函数转换成一系列使用一个参数的函数的技术。而对于Javascript 语言来说,我们通常说的柯里化函数的概念,与数学和计算机科学中的柯里化的概念并不完全一样。在数学和计算机科学中的柯里化函数,一次只能传递一个参数;而 Javascript 实际应用中的柯里化函数,可以传递一个或多个参数。

函数柯里化的作用

降低函数的通用性, 提高函数的适用性, 使其拥有更高的自由度;
我们通常要求函数封装越通用越好, 这样可以适应各种应用场景, 实现函数复用, 但这也导致我们在某个场景内使用时需要重复输入参数, 柯里化可以很好的实现参数复用; 常用于表单校验等

总结来讲,函数柯里化的一大作用是实现 参数复用

例子

工作中我们经常会遇到各种需要通过检验的需求,比如校验电话号码、校验邮箱、校验身份证号、校验密码等, 这时我们会封装一个通用函数 check ,接收两个参数:校验的对象类型 (例如是校验手机号码还是校验邮箱) 和待校验的字符串。

// 通用校验函数
function check(type,string) {
    return type.test(string);  
}

check('telephone', '18642838455'); // 校验电话号码
check('e-mail', '[email protected]'); // 校验邮箱

上面这段代码,乍一看没什么问题,可以满足我们所有检验需求。 但是我们考虑这样一个问题,如果我们需要校验多个电话号码或者校验多个邮箱呢?按照上述写法,你可以轻松写出如下代码:

// 校验电话号码
check('telephone', '18642838455'); 
check('telephone', '13109840560'); 
check('telephone', '13204061212'); 

// 校验邮箱
check('e-mail', '[email protected]'); 
check('e-mail', '[email protected]'); 
check('e-mail', '[email protected]'); 

问题出现了,对于相同类型的对象校验,我们每次进行校验的时候都需要重复输入第一个参数,导致我们重复书写参数的原因就是我们原先设计的 check 函数通用性太强了,它可以接收并检验任何类型的对象,前提是我们必须传入一个指定对象类型的参数。这就导致我们在使用的时候效率低下,此外,如果没有注释,我们必须通过检查传入 check 函数的第一个参数,才能知道我们校验的是电话号码还是邮箱,还是别的什么。我们对通用函数 check 进行柯里化,看看会发生什么改变:

//进行柯里化
let _check = curry(check);
//生成工具函数,验证电话号码
let checkCellPhone = _check('telephone');
//生成工具函数,验证邮箱
let checkEmail = _check('e-mail');

// 校验电话号码
checkCellPhone('18642838455'); 
checkCellPhone('13109840560'); 
checkCellPhone('13204061212');

// 校验邮箱
checkEmail('[email protected]'); 
checkEmail('[email protected]'); 
checkEmail('[email protected]'); 

柯里化实现了函数参数的复用!!!
如上所示,check 通用函数经过柯里化之后,可以逐个接收参数,从而定制出专门检验某个类型的新函数。借助柯里化对 check 函数进行封装,可以简化代码书写,并提高代码可读性。

函数柯里化通用封装

封装思路

执行流程:当接收的参数数量小于原函数的形参数量(不包含默认值)时,返回一个函数用于接收剩余的参数,直至接收的参数数量与形参数量一致,执行原函数;

  1. 创建一个数组用于存放已接收的参数
  2. 通过比较当前接收参数个数和目标函数的形参个数, 判断是直接执行目标函数还是继续返回函数接收参数

代码

// 函数柯里化
/**
 * curry
 * @param {Function} fn // 接收被柯里化的目标函数名
 * @param {Array} arr // 存储已传递的参数
 * @returns // 若函数参数未传递满, 则返回函数接收剩余参数; 若传递完成则执行目标函数
 */

function curry(fn, arr = []) {
  // fn.length <=> arguments.callee.length : 获取函数形参个数(不包含默认值)
  let lens = fn.length;
  // 返回一个函数接受剩余参数
  return function func (...args) {
    // 将剩余参数拼接到参数列表
    arr = arr.concat(args)
    if (arr.length < lens) {
      // 注意此处若用 apply, 需要将多个参数用 [] 包裹, 因为 apply 参数只能接受数组或类数组对象
      // call 接收多个参数, 并通过 Spread 语法统一到参数数组内, 即 fn.call(context, ...args)
      // apply / call 立即执行, 因此此处返回的是 func()
      return curry.call(this, fn, arr)
      // return curry.apply(this, [fn, arr])
    } else {
      return fn.apply(this, arr)
    }
  }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant