函数柯里化实现 add(1)(2)(3)

1,807 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第3天,点击查看活动详情

背景解读

实现函数调用 add(1)(2)(3) 后,输出值为 1+2+3=6 的结果。

核心描述

  • 柯里化定义:将一个使用多个参数的函数,转换成一系列使用一个函数的技术。
  • 初级实现版本
    /**
	 * 仅支持 add(1)(2)(3) 的调用方式
	 */
	function add(x){
		return function(y) {
			return function(z) {
				return x+y+z
			}
		}
	}
	console.log(add(1)(2)(3)) // 输出 6
  • 通用实现版本
    /**
	 * 支持调用方式包括: 
	 * add(1)(2)(3) 
	 * add(1,2)(3)
	 * add(1,2,3)
	 */

	function addNumber(x,y,z){
		return x+y+z
	}

	// 柯里化函数
	function curry(fn,...args){
		// 如果参数的长度大于等于被包装的函数的入参数
		// 则直接执行被包装的函数,并将入参数依次传入
		// 此处为递归的出口,如果不满足此条件,将递归执行 curry 方法
		if(args.length >= fn.length) {
			return fn(...args)
		} else {
			// 否则返回一个函数,将已传入的参数 args 提前传入 curry 中
			// 将新传入的参数 _args 也传入到 curry 中
			// 对 curry 柯里化函数递归调用
			return function(..._args) {
				return curry(fn,...args,..._args)
			}
		}
	}
	// 获取经过 curry 柯里化包装后的 add 方法
	const add = curry(addNumber)
	console.log(add(1)(2)(3)) // 输出 6
	console.log(add(1,2,3))   // 输出 6
	console.log(add(1,2)(3))  // 输出 6
  • 高级实现版本
	/**
	 * 支持以下输出示例:
	 * add(1)(2).valueOf() 		  // 输出 3
	 * add(1,2).valueOf() 		  // 输出 3
	 * add(1)(2)(3).valueOf() 	  // 输出 6
	 * add(1,2)(3).valueOf() 	  // 输出 6
	 * add(1)(2)(3)(4).valueOf()  // 输出 10
	*/

	function add(...args){
		// 定义一个 func 函数对象
		// 实质上是用来不断的保存参数,直到方法统一调用时,即 valueOf 方法被调用时
		let func = function(..._args){
			return add(...args,..._args)
		}
		// 重写函数对象的 valueOf 方法
		// 此方法为递归调用的结束
		// 如果不调用此方法,则表示还有其他参数,则会继续返回 func 
		// 如果想用别的方式停止,编写对应逻辑即可
		// 如:当执行方法不传入参数时,则判断一下入参长度 _args.length === 0,为 true 则执行
		// valueOf 中现有的逻辑即可
		func.valueOf = function(){
			console.log(args)
			return args.reduce((a,b)=>a+b)
		}
		return func
	}
	
	console.log(add(1)(2).valueOf()) 		// 输出 3
	console.log(add(1,2).valueOf())   		// 输出 3
	console.log(add(1)(2)(3).valueOf()) 	// 输出 6
	console.log(add(1,2)(3).valueOf())		// 输出 6
	console.log(add(1)(2)(3)(4).valueOf())  // 输出 10
  • 实现核心:
    • 判断传入的参数 args 与原始函数定义的长度比较,如果 args 更长或者相同,则可以直接执行原始函数,否则递归柯里化函数
    • 利用闭包将函数的参数储存起来,等参数达到一定数量时执行函数。
  • 柯里化的好处:
    • 参数复用
    • 延迟执行:如节流、防抖、bind 函数的 polyfill 实现
    • 提前返回:比如封装一个 addEvent,提前返回之后,下一次就不会再进入对应的浏览器环境判断了,减少了执行时间
      • 函数中有 if...else... 或者其他分支判断
      • 函数返回的是一个函数,而不是一个值

知识拓展

  • 纯函数,满足下面2个条件的函数,就可以称为纯函数:
    • 如果函数的调用参数相同,则永远返回相同的结果。它不依赖于程序执行期间函数外部任何状态或数据的变化,必须只依赖于其输入参数。
    • 该函数不会产生任何可观察的副作用,例如网络请求,输入和输出设备或数据突变(mutation)。
  • 高阶函数,就是一个将函数作为参数或者返回值的函数。
  • 偏函数,是 JS 函数柯里化运算的一种特定应用场景。简单描述,就是把一个函数的某些参数先固化,也就是设置默认值,返回一个新的函数,在新函数中继续接收剩余参数,这样调用这个新函数会更简单。
  • 函数柯里化,将一个使用多个参数的函数,转换成一系列使用一个函数的技术。
  • 函数反柯里化,是一个泛型化的过程。它使得被反柯里化的函数,可以接收更多参数。目的是创建一个更普适性的函数,可以被不同的对象使用。有鸠占鹊巢的效果。
    • 举例:Array.prototype.push 的方法一般只用于数组,但是通过 Array.prototype.push.call(null,{}) 的调用,可以用于类似 function 的 arguments 参数等等。
  • bind 实现,其实也是一种柯里化的应用。

参考资料

浏览知识共享许可协议

本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。