Curried Function

10 阅读3分钟

函数柯里化

函数柯里化又叫部分求值。

函数柯理化的表现:把一个需要传入多个变量的函数变为多个嵌套函数,并且内层函数会调用上层函数的变量。

定义:返回一个从接受多个参数,变成只接受单一参数的函数。

柯里化之后的函数不会立即执行,会把传入的参数通过闭包保存起来,直到真正求值的时候,之前的参数都会被一次性用于求值。

柯理化利用模块化思想处理多参函数,通过组合函数减少每个函数的入参数量,从而提高代码的可阅读性及可维护性。

 // 简单的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))