通俗易懂让你学会柯里化函数

前言

本文旨在以最少的时间,让你学会手写柯里化函数,因此大量使用类比和比喻,争取让一个没有接触过函数式编程思想的小白也能学会。

由于本人在学习函数式编程的过程中,发现手写柯里化函数和组合函数是面试高频考点。于是将自己学习的笔记发布出来,供掘友们参考,一同学习💪。

函数柯里化

什么是函数柯里化?

举一个例子🌰:

假设一个函数需要三个参数才能运行,那么函数柯里化就是将这个函数拆分成三个函数,每个函数只需要一个参数。

js 复制代码
//普通函数
function sum(a, b, c){
    return a+b+c
}

//柯里化后的函数
function currySum(a){
	return function(b){
		return function(c){
			return a+b+c
		}
	}
}

最后这两个函数都实现了同样的功能,接收三个参数,最后返回三个参数的和

js 复制代码
console.log(sum(1,2,3))
console.log(currySum(1)(2)(3))

那么为什么需要函数柯里化呢?答案是为了灵活的使用函数。这就好比我饿了,需要吃一整块吐司🍞才能吃饱。直接调用函数就是一口吃掉这一整块吐司🍞。而函数柯里化就是将这一整块吐司🍞切成了一片一片的吐司片,我可以拿起一片吐司涂上蓝莓酱,吃下去;然后再拿起一片吐司涂上草莓酱,再吃下去......最后把整块吐司🍞吃完。

举个例子🌰,假设我有一个函数可以将输入的两个参数相乘

js 复制代码
function multiply(a, b){
	return a*b
}

现在我有一个新的需求,我需要一个函数,可以将输入的参数乘以2,然后返回。这时候我们发现,这个需求可以通过我们写的multiply函数实现

js 复制代码
const num = 5
const doubleNum = multiply(2, num)
console.log(doubleNum)

然后公司的业务不断扩张,我需要给100个数乘以2,然后返回。这个时候我们虽然也可以继续通过multiply函数实现,但是每次都要输入一个固定参数2,未免太不优雅。

而且时间长了后,我们还有其他业务也是通过multiply函数实现的,比如将输入的参数乘以3,然后返回、将输入的参数乘以4,然后返回......

最终我们的代码里到处都充斥着multiply()函数,而且他们的区别只有输入的参数不同,过了一个月后,我自己都不认识我的代码了

js 复制代码
const doubleNum = multiply(2, num)
const tripleNum = multiply(3, num)
const fourTimesNum = multiply(4, num)
const tripleNum = multiply(3, num)
const doubleNum = multiply(2, num)

这个时候就该我们的函数柯里化上场了,我们可以将两个参数相乘的multiply函数切成两个函数

js 复制代码
function curryMultiply(a){
	return function(b){
		return a*b
	}
}

但是,我们将函数切成了两个后,又有什么用呢?别急!别眨眼,下面是见证奇迹的时刻🧙‍

js 复制代码
const num = 2
const double = curryMultiply(2)

const doubleNum = double(num)
console.log(doubleNum)
//4

欸!我们成功通过函数柯里化,将两数相乘的multiply()函数切成了两个函数,然后通过传入一个固定参数2,获得了一个工具函数double,这个工具函数将输入的参数乘以2然后返回。也就是说:

通过函数柯里化,可以实现函数参数的复用

有没有感觉思路被打开了!(○´・д・)ノ

上文中我们针对特定的multiply函数,实现了函数柯里化。但是函数柯里化的极限并不在这里,我们可以编写一个通用的柯里化函数,这个通用的柯里化函数可以对所有函数进行柯里化,将接收多个参数的函数转化成接收一个参数的多个函数

下面才是真正的难点,让我们深呼吸😌,吃一口涂满蓝莓酱的吐司片🍞。准备好了吗?我们继续探索真正的柯里化函数

手写柯里化函数

前提知识

在我们开始手写柯里化函数之前,让我确保大家都了解以下的前置知识点:

  • 函数的.length属性,返回函数预期参数的个数
js 复制代码
function sum(a, b, c){}
console.log(sum.length)
//输出3,代表函数接收三个参数
  • 通过扩展运算符...函数可以接收任意数量的参数,这些参数存放到args数组里
js 复制代码
function print(...args){
	console.log(args)
}
print(a, b, c, d)
//输出 [a, b, c, d]
  • 数组的.concat()方法可以将传入的数组添加到前面数组的尾部,然后返回一个新的数组,不修改原值
js 复制代码
let arr1 = [1, 2, 3, 4]
let arr2 = [1, 2, 3]
let concatArr = arr1.concat(arr2)
console.log(concatArr)
//输出 [1, 2, 3, 4, 1, 2, 3]
  • 函数的.call()方法可以执行函数,同时绑定this值、传入参数(一个个传入参数)
js 复制代码
function hello(name1, name2){
	console.log("hello " + name1 + " " + name2)
}
hello.call(this, "李卢", "李大卢")
//输出 hello 李卢 李大卢
  • 函数的.apply()方法也可以执行函数,同时绑定this值、传入参数(将参数放入数组中,传入数组)
js 复制代码
function hello(name1, name2){
	console.log("hello " + name1 + " " + name2)
}
hello.apply(this, ["李卢", "李大卢"])
//输出 hello 李卢 李大卢

开始手写柯里化函数

恭喜你已经学习完所有前置知识!现在赶快给面试官手搓一个柯里化函数吧! 跟着我的注释,一起敲一个柯里化函数吧( •̀ ω •́ )y

js 复制代码
function Fn_init(a, b, c, d) {
    console.log("最终结果:", a + b + c + d);
}

//定义一个curry函数,用于实现函数柯里化。函数柯里化指的是一种,将接收多个参数的函数,转化为多个使用单一函数的技术。
//curry函数接收两个参数,对函数fn进行函数柯里化,params是一个数组,用来预设fn函数的参数,如果不传递params,即不预设fn函数的参数的话,params为undefined
function curry(fn, params) {
	//通过函数的.length属性,获取函数预期参数个数,然后赋值给length
	let length = fn.length
	//如果没有输入参数的话,默认为undefined,将其赋值为空数组
	//这段代码用来确保params是一个数组
	params = params || []
	console.log("params", params)

	//返回fn柯里化后的函数,这个函数通过扩展运算符... 可以接收任意数量的的参数,接受的参数放入args数组里,
	//args数组里的参数将被逐个收集,以供原始函数fn调用
	return function (...args) {
		//通过数组的concat方法,将预设的参数params与柯里化函数输入的参数组合起来,赋值给newArgs
		//newArgs数组用来存放已经收集到的参数
		let newArgs = params.concat(args)
		console.log("newArgs: ", newArgs)

		//将当前收集到的参数数量 与 原始函数所需的参数数量做对比
		if (newArgs.length < length) {
			//如果没有收集到足够的参数
			//通过递归调用curry函数,通过curry()函数的.call()方法,绑定当前上下文的this,
			//传入原始函数fn和已经收集到的函数参数newArgs,等待下一次函数调用,然后继续收集参数
			return curry.call(this, fn, newArgs)
		}
		else {
			//收集到了足够的参数后,调用原始函数
			//通过原始函数的apply方法,调用原始函数。this绑定调用这个函数的上下文,传入参数数组newArgs
			return fn.apply(this, newArgs)
		}
	}
}

const curryFunc = curry(Fn_init)

console.log(curryFunc(2)(3)(4)(5))
//输出:最终结果:14
相关推荐
勿语&23 分钟前
Element-UI Plus 暗黑主题切换及自定义主题色
开发语言·javascript·ui
黄尚圈圈25 分钟前
Vue 中引入 ECharts 的详细步骤与示例
前端·vue.js·echarts
浮华似水1 小时前
简洁之道 - React Hook Form
前端
正小安3 小时前
如何在微信小程序中实现分包加载和预下载
前端·微信小程序·小程序
_.Switch5 小时前
Python Web 应用中的 API 网关集成与优化
开发语言·前端·后端·python·架构·log4j
一路向前的月光5 小时前
Vue2中的监听和计算属性的区别
前端·javascript·vue.js
长路 ㅤ   5 小时前
vite学习教程06、vite.config.js配置
前端·vite配置·端口设置·本地开发
长路 ㅤ   5 小时前
vue-live2d看板娘集成方案设计使用教程
前端·javascript·vue.js·live2d
Fan_web5 小时前
jQuery——事件委托
开发语言·前端·javascript·css·jquery
安冬的码畜日常5 小时前
【CSS in Depth 2 精译_044】第七章 响应式设计概述
前端·css·css3·html5·响应式设计·响应式