【js进阶】-函数柯里化

一、高阶函数

在正式聊函数柯里化之前,我这里给大家补充下高阶函数的概念和应用:

1.1、什么是高阶函数?

高阶函数英文叫 Higher-order function,它的定义很简单,就是至少满足下列一个条件的函数:

  • 接受一个或多个函数作为输入
  • 输出一个函数

也就是说高阶函数是对其他函数进行操作的函数,可以将它们作为参数传递,或者是返回它们。 简单来说,高阶函数是一个接收函数作为参数传递或者将函数作为返回值输出的函数。

1.2、高阶函数例子

其实平时开发中经常会用到高阶函数,只是之前你不清楚其概念而已,来看下下面几个函数:

  1. Array.prototype.map
  2. Array.prototype.filter
  3. Array.prototype.reduce
  4. Array.prototype.sort

这三个数组的方法函数就是高阶函数,因为它们都接收一个回调函数作为参数,满足第一个条件

高阶函数其中一个优点就是在某种情况下可以让代码更加简洁,我们就拿map方法来举例,如果不用map这个高阶方法的话,要实现对一个现有数组中每个元素*2的需求

不使用高阶函数:

const arr1 = [1, 2, 3, 4];
const arr2 = [];
for (let i = 0; i < arr1.length; i++) {
  arr2.push( arr1[i] * 2);
}
console.log( arr2 );
// [2, 4, 6, 8]
console.log( arr1 );
// [1, 2, 3, 4]

使用map高阶函数:

const arr1 = [1, 2, 3, 4];
const arr2 = arr1.map(item => item * 2);
console.log( arr2 );
// [2, 4, 6, 8]
console.log( arr1 );
// [1, 2, 3, 4]

从上面例子中不难看出用map方法可以轻松实现上述需求,其他两个方法其实也是同样的道理,这里就不多叙述了

二、函数柯里化

2.1、定义

函数柯里化又叫部分求值,维基百科中对柯里化 (Currying) 的定义为:

在数学和计算机科学中,柯里化是一种将使用多个参数的函数转换成一系列使用一个参数的函数,并且返回接受余下的参数而且返回结果的新函数的技术。

用大白话来说就是只传递给函数一部分参数来调用它,让它返回一个新函数去处理剩下的参数

相信部分同学看完这个概念还是有点稀里糊涂,我们来看个简单例子说明下

function url (a,b,c){
	return `${a}://www.${b}.${c}`
}
const url1 = url('https',"baidu",'com')
console.log(url1)    // "https://www.baidu.com"

上面这个url函数接受三个参数,最终函数返回一个完整的url地址,这是普通写法,然后我们来看下函数柯里化后的写法是怎么样的?

function url(a){
	return (b)=>{
		return (c)=>{
			return `${a}://www.${b}.${c}`
		}
	}
}
const AA = url('https')   // 他是一个函数
const BB = AA('baidu')   // 他是一个函数
const CC = BB('com')   // 他是一个函数
console.log(CC)  // "https://www.baidu.com"

看到这我们再回过头来看下其概念定义,原先url函数接受多个参数,然后使用了柯里化技术,将其转换成了一系列接收一个参数的函数(url改写后内部就是返回多个接收单一入参的函数),并且返回接受余下的参数(url原先的其他参数b和c现通过其内部返回的其他函数来接收处理了),最终函数柯里化后还是会返回一个新结果的函数(上面例子最终返回的是一个接收参数c返回最终完整结果的函数),现在会不会好理解些了呢

2.2、柯里化的应用(优点)

1、参数复用

还是可以用上面拼接完整url的例子来说明

// DEMO1
//原先的写法
function url (a,b,c){
	return `${a}://www.${b}.${c}`
}
const url1 = url('https','baidu','com')
const url2 = url('https','jingdong','com')

//柯里化处理后
function url(a){
	return (b)=>{
		return (c)=>{
			return `${a}://www.${b}.${c}`
		}
	}
}
const json1 = url('https');
const url1 = json1('baidu')('com')
const url2 = json1('jindong')('com')

通过对函数柯里化处理后,在生成多个类似url地址时就可以复用相同的部分,不用重复传相同的参数,起到了参数复用的效果,我们还可以再继续巩固下,再来看个例子:

//DEMO2
// 原先正则校验字符串函数每次调用时需要重复传入正则表达式,如果正则表达式很长的话,是不是会显得繁琐
function check(regexp,str){
	return regexp.test(str)
}
const result1 = check(/\d+/,'456')   // true
const result2 = check(/\d+/,'asd')   // false

// 函数柯里化后
function check(regexp){
	return (str)=>{
		return regexp.test(str)
	}
}
const temp = check(/\d+/)
const result1 = temp('456')  // true
const result2 = temp('asd')  // false

2、延迟调用

js中的bind方法其实底层原理也是应用了柯里化技术,我们来看下代码:

// 下面是bind方法的简易版代码
function bind (ctx,...args){
	return ()=>{
		return this.apply(ctx,args)  // 这里的this指的就是调用bind方法的那个函数
	}
} 
var name = "windowName";  //全局name
var age = 22;        // 全局age
const obj = {name: 'objName',age:11}
function zhangsan (){
	return `${this.name}-${this.age}`
}
zhangsan()   //'windowName-22'
zhangsan.bind(obj)   // ƒ zhangsan (){return `${this.name}-${this.age}`}
zhangsan.bind(obj)()  //'objName-11'

说明:从上面bind方法可以看出,调用bind方法后它只是改变了内部this指向,并返回了一个函数,并没有立即执行该函数,如果想要得到结果,需要再次调用bind方法返回的那个函数才行,所以这点也就说明了柯里化函数有延迟调用的作用

2.3、手写万能柯里化函数

我们可以这么理解:所谓的柯里化函数,就是封装「一系列的处理步骤」,通过闭包将参数集中起来计算,最后再把需要处理的参数传进去。那如何实现 currying 函数呢?

实现原理就是「用闭包把传入参数保存起来,当传入参数的数量足够执行函数时,就开始执行函数」。上面延迟计算部分已经实现了一个简化版的 currying 函数。

function currying (fn,...args){
	if(args.length>=fn.length){  //参数够了直接执行函数返回结果
		return fn(...args)
	}
	//参数不够,递归调用
	return (...rest)=>{
		return currying(fn,...args,...rest);
	}
}

好,我们来试下效果:

//我们定义一个add函数
function add (a,b,c){
	return a+b+c
}
//然后通过我们封装好的currying函数,传入add函数,生成一个柯里化后的函数
const curryAdd = currying(add)

//然后再给这个柯里化处理后的函数传参,此时分开传也是可以的
console.log(curryAdd(1,2)(3))
console.log(curryAdd(1)(2,3))
console.log(curryAdd(1,2,3))
2.4、拓展(函数参数length)

在上面例子中,使用了 fn.length 来表示函数参数的个数,那 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 的函数参数默认值。

const fn = currying((a = 1, b, c) => {
  console.log([a, b, c]); 
}); 

fn();
// [1, undefined, undefined]

fn()(2)(3); 
// Uncaught TypeError: fn(...) is not a function

我们期望函数 fn 输出 [1, 2, 3],但是实际上调用柯里化函数时 ((a = 1, b, c) => {}).length === 0,所以调用 fn() 时就已经执行并输出了 [1, undefined, undefined],而不是理想中的返回闭包函数,所以后续调用 fn()(2)(3) 将会报错

  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ronychen’s blog

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值