前语

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

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

函数柯里化

什么是函数柯里化?

举一个比方:

假定一个函数需求三个参数才干运行,那么函数柯里化便是将这个函数拆分红三个函数,每个函数只需求一个参数。

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

终究这两个函数都完成了同样的功能,接纳三个参数,终究回来三个参数的和

console.log(sum(1,2,3))
console.log(currySum(1)(2)(3))

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

举个比方,假定我有一个函数能够将输入的两个参数相乘

function multiply(a, b){
	return a*b
}

现在我有一个新的需求,我需求一个函数,能够将输入的参数乘以2,然后回来。这时分咱们发现,这个需求能够经过咱们写的multiply函数完成

const num = 5
const doubleNum = multiply(2, num)
console.log(doubleNum)

然后公司的业务不断扩张,我需求给100个数乘以2,然后回来。这个时分咱们尽管也能够持续经过multiply函数完成,但是每次都要输入一个固定参数2,未免太不优雅。

并且时间长了后,咱们还有其他业务也是经过multiply函数完成的,比方将输入的参数乘以3,然后回来、将输入的参数乘以4,然后回来……

终究咱们的代码里到处都充斥着multiply()函数,并且他们的区别只要输入的参数不同,过了一个月后,我自己都不知道我的代码了

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函数切成两个函数

function curryMultiply(a){
	return function(b){
		return a*b
	}
}

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

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

欸!咱们成功经过函数柯里化,将两数相乘的multiply()函数切成了两个函数,然后经过传入一个固定参数2,获得了一个东西函数double,这个东西函数将输入的参数乘以2然后回来。也便是说:

经过函数柯里化,能够完成函数参数的复用

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

上文中咱们针对特定的multiply函数,完成了函数柯里化。但是函数柯里化的极限并不在这里,咱们能够编写一个通用的柯里化函数,这个通用的柯里化函数能够对一切函数进行柯里化,将接纳多个参数的函数转化成接纳一个参数的多个函数

下面才是真实的难点,让咱们深呼吸,吃一口涂满蓝莓酱的吐司片。预备好了吗?咱们持续探索真实的柯里化函数

手写柯里化函数

条件常识

在咱们开始手写柯里化函数之前,让我保证我们都了解以下的前置常识点:

  • 函数的.length特点,回来函数预期参数的个数
function sum(a, b, c){}
console.log(sum.length)
//输出3,代表函数接纳三个参数
  • 经过扩展运算符...函数能够接纳恣意数量的参数,这些参数存放到args数组里
function print(...args){
	console.log(args)
}
print(a, b, c, d)
//输出 [a, b, c, d]
  • 数组的.concat()办法能够将传入的数组添加到前面数组的尾部,然后回来一个新的数组,不修正原值
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值、传入参数(一个个传入参数)
function hello(name1, name2){
	console.log("hello " + name1 + " " + name2)
}
hello.call(this, "李卢", "李大卢")
//输出 hello 李卢 李大卢
  • 函数的.apply()办法也能够履行函数,一同绑定this值、传入参数(将参数放入数组中,传入数组)
function hello(name1, name2){
	console.log("hello " + name1 + " " + name2)
}
hello.apply(this, ["李卢", "李大卢"])
//输出 hello 李卢 李大卢

开始手写柯里化函数

恭喜你现已学习完一切前置常识!现在赶快给面试官手搓一个柯里化函数吧!
跟着我的注释,一同敲一个柯里化函数吧( • • )y

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