函数柯里化
函数柯里化又叫部分求值。
函数柯理化的表现:把一个需要传入多个变量的函数变为多个嵌套函数,并且内层函数会调用上层函数的变量。
定义:返回一个从接受多个参数,变成只接受单一参数的函数。
柯里化之后的函数不会立即执行,会把传入的参数通过闭包保存起来,直到真正求值的时候,之前的参数都会被一次性用于求值。
柯理化利用模块化思想处理多参函数,通过组合函数减少每个函数的入参数量,从而提高代码的可阅读性及可维护性。
// 简单的add 函数
function add(x, y) {
return x + y
}
console.log(add(1, 2)) // 3
// 改为柯理化函数
function add2(x) {
return function (y) {
return x + y
}
}
console.log(add2(1)(2)) // 3
柯理化函数的核心是闭包,因为闭包,所以内层函数才能够保留父作用域中的变量。
通用柯理化函数
// 创建通用的柯理化函数
function currying(fun) {
var args = Array.prototype.slice.call(arguments)
return function () {
var _args = args.concat(Array.prototype.slice.call(arguments))
return fun.apply(null, _args)
}
}
// currying 函数将除了第一个fn 变量,将其他参数变量都赋值在args 变量中,然后将其合并在返回函数的变量参数中,给返回函数使用。
// 利用currying 改造一个支持多个入参的add 函数(采用ES6 语法)
// 支持多个入参
function add(...vals) {
return vals.reduce((pre, val) => {
return pre + val
})
}
var newAdd = currying(add, 1, 2, 3)
console.log(newAdd(4, 5, 6)) // 21
// 这种方式创建的柯理化函数只能调用一次。
// 柯理化工厂函数,可以多次调用,最后统一求值
function currying(fn) {
var args = Array.prototype.slice.call(arguments)
// var args = [].slice.call(arguments)
return function () {
if (arguments.length === 0) {
return fn.apply(this, args)
} else {
Array.prototype.push.apply(args, arguments)
return arguments.callee
}
}
}
// 利用上面的currying 改造add 函数:
function add() {
var items = Array.prototype.slice.call(arguments)
return items.reduce((pre, val) => {
return pre + val
})
}
var newAdd = currying(add, 1, 2, 3)
newAdd(4, 5)
newAdd(6, 7)
console.log(newAdd()) // 28
// 把每次函数调用的参数都存储起来,如果已无参形式调用,说明记录结束,需要做最终计算。
// 有问题,仅供参考
function add() {
var _args = Array.prototype.slice.call(arguments)
var _adder = function() {
_args.push(...arguments)
return _adder
}
// toString 隐形转换的特性(return 一个函数会隐式转换成string 类型,所以要覆写toString 方法)
_adder.toString = function() {
return _args.reduce(function(a, b) {
return a + b
}, 0)
}
return _adder
}
函数参数length
函数 currying 的实现中,使用了 fn.length
来表示函数参数的个数。
函数的 length 属性获取的是形参的个数,但是形参的数量不包括剩余参数个数,而且仅包括第一个具有默认值之前的参数个数。
((a, b, c) => {}).length // 3
((a, b, c = 3) => {}).length // 2
((a, b = 2, c) => {}).length // 1
((a = 1, b, c) => {}).length // 0
((...args) => {}).length // 0
const fn = (...args) => {
console.log(args.length)
}
fn(1, 2, 3) // 3
所以在柯里化的场景中,不建议使用 ES6 的函数参数默认值。
应用
- 参数复用:XHR 请求、正则匹配函数
- 提前返回:不同环境执行不同方法
- 延迟计算:部分求和
// 实现
add(1, 2, 3) // 6
add(1, 2)(3) // 6
add(1)(2)(3) // 6
add(1)(2, 3) // 6
// 解法
function currying(fn, length) {
length = length || fn.length
return function (...args) {
return args.length >= length
? fn.apply(this, args)
: currying(fn.bind(this, ...args), length - args.length)
}
}
const sum = function(t,y,u){
let args = [].slice.call(arguments)
return args.reduce((a, b) => a + b)
}
const add = currying(sum)
console.log(add(1, 2)(2)(9))