柯里化
function checkAge (age) {
let min = 18
return age >= min
}
目前代码中是存在硬编码的 min
,想要解决这种硬编码的方式也很简单,只要把硬编码的方式提取到参数中就好了
function checkAge(min,age){
return age >= min
}
//test
console.log(checkAge(18,15));
console.log(checkAge(18,20));
console.log(checkAge(20,22));
但是这样又有了一个新的问题,如果是重复的 min
值,我们需要重复调用。这时可以通过闭包来解决
function checkAge(min){
return function(age){
return age >= min
}
}
//es6写法
let chekAge = min => ( age => age >= min )
// test
const checkAge18 = checkAge(18);
console.log(checkAge18(20));
上述就是简单实现函数柯里化的过程
定义(Currying)
- 当一个函数有多个参数的时候先传递一部分参数调用它(这部分参数以后永远不变),
- 然后返回一个新的函数接收剩余的参数,返回结果
Lodash中的柯里化
_.curry(func)
- 功能:创建一个函数,该函数接收一个或多个 func 的参数,如果 func 所需要的参数都被提 供则执行 func 并返回执行的结果。否则继续返回该函数并等待接收剩余的参数。
- 参数:需要柯里化的函数
- 返回值:柯里化后的函数
使用
// demo1
const _ = require("lodash");
const add = (a, b, c) => a + b + c;
const curried = _.curry(add);
// test
console.log(add(1, 2, 2))
console.log(curried(1)(2)(3))
console.log(curried(1, 2)(3))
console.log(curried(1,)(2, 3))
console.log(curried(1, 2)) //有兴趣可以看下这行输出什么
案例
提取字符串中的全部空白字符或数字
// demo2
''.match(/\s+/g)
''.match(/\d+/g)
const _ = require("lodash");
const match = _.curry((reg, str) => str.match(reg));
const haveSpace = match(/\s+/g);
const haveNumber = match(/\d+/g);
//test
console.log(haveNumber('46815dddd'));
console.log(haveSpace("sss"))
console.log(haveSpace(" sss"))
const filter = _.curry((func, arry) => arry.filter(func));
const findSpace = filter(haveSpace);
// 过滤数组中包含空格的元素
console.log(findSpace([" ff","sss","dd0 "]))
原理
- 柯里化后的函数,如果传入全部参数,立即执行函数
- 如果传入部分参数,返回一个新的函数,等待传递参数完整后,立即执行函数
// demo3
const add = (a, b, c) => a + b + c;
function curry(func) {
// 判断实参和形参的个数
return function curriedFn(...args) {
if (args.length < func.length) {
// 形参args个数,少于实参个数(方法名.length)时,返回新函数
return function () {
// 将当前的argument和上次的rest参数args,合并递归调用匿名函数
// arguments是伪数组,通过Array.from转为数组,并将数组展开
return curriedFn(...args.concat(Array.from(arguments)))
}
} else {
// 传入全部参数,立即执行函数
return func(...args)
}
}
}
const curryEs6 = (func) => {
const curriedFn = (...args) => {
if (args.length < func.length) {
// 箭头函数不可以继续使用arguments,但是可以使用 rest 参数来代替arguments
return (...values) => curriedFn(...args.concat(values))
} else {
return func(...args)
}
}
return curriedFn;
}
const curried = curry(add);
const curried2 = curryEs6(add);
console.log(curried(1)(2)(3))
console.log(curried(1, 2)(3))
console.log(curried(1,)(2, 3))
console.log(curried(1, 2))
console.log(curried2(1)(2)(3))
console.log(curried2(1, 2)(3))
console.log(curried2(1,)(2, 3))
console.log(curried2(1, 2))
思考
在 ES6 中,函数新增了形参默认值,然而当函数使用形参默认值时,rest 参数的length属性,将获取不到设置了默认值的变量,此时如果去做柯里化会发生什么呢?
实验后出现如下结果:
// demo3代码,修改add方法为:
const add = (a, b=2, c) => a + b + c;
// Run Result
console.log(curried(1)(2)(3))
^
TypeError: curried(...) is not a function
at Object.<anonymous> (D:\xxx\03.custom.js:34:23)
at Module._compile (internal/modules/cjs/loader.js:1138:30)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:1158:10)
at Module.load (internal/modules/cjs/loader.js:986:32)
at Function.Module._load (internal/modules/cjs/loader.js:879:14)
at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:71:12)
at internal/main/run_main_module.js:17:47
那么lodash是如何处理的呢,能否避过这个问题呢,使用lodash的实验结果如下:
// demo3代码,修改add方法为:
const add = (a, b=2, c) => a + b + c;
// Run Result
console.log(curried(1)(2)(3))
^
TypeError: curried(...) is not a function
at Object.<anonymous> (D:\xxx\03-柯里化.js:8:23)
at Module._compile (internal/modules/cjs/loader.js:1138:30)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:1158:10)
at Module.load (internal/modules/cjs/loader.js:986:32)
at Function.Module._load (internal/modules/cjs/loader.js:879:14)
at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:71:12)
at internal/main/run_main_module.js:17:47
lodash的结果和我们的结果是一致的!
为什么使用函数形参默认值的时候,会出现这样的情况呢?
回到函数式编程上,使用函数形参默认值的形式,相当于在代码中进行了参数硬编码,如同文章开头写的这样,而柯里化就是为了避免这种情况,出发点是矛盾的,因此报错是符合预期的行为。
function checkAge (age) {
let mini = 18
return age >= mini
}
总结
- 柯里化可以让我们给一个函数传递较少的参数得到一个已经记住了某些固定参数的新函数(lodash中的柯里化—>案例)
- 这是一种对函数参数的'缓存'
- 让函数变的更灵活,让函数的粒度更小
- 可以把多元函数转换成一元函数,可以组合使用函数产生强大的功能
通过纯函数和柯里化,我们已经能写出符合函数式编程要求的代码,但是还会存在一些小问题,下篇接着写