-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
【进阶 6-2 期】深入高阶函数应用之柯里化 #37
Comments
This was referenced May 29, 2019
hello,这个例子: function currying(fn, length) {
length = length || fn.length; // 注释 1
return function (...args) { // 注释 2
return args.length >= length // 注释 3
? fn.apply(this, args) // 注释 4
: currying(fn.bind(this, ...args), length - args.length) // 注释 5
}
}
// Test
const fn = currying(function(a, b, c) {
console.log([a, b, c]);
});
fn("a", "b", "c") // ["a", "b", "c"]
fn("a", "b")("c") // ["a", "b", "c"]
fn("a")("b")("c") // ["a", "b", "c"]
fn("a")("b", "c") // ["a", "b", "c"] 可以不用在递归 currying 的时候传入 length - args.length,因为 bind 返回的函数的 length 已经是 length - args.length 了: function currying(fn) {
return function (...args) {
return args.length >= fn.length
? fn.apply(this, args)
: currying(fn.bind(this, ...args))
}
} |
太难了 |
看不懂啊 被自己蠢死 |
# for free
to join this conversation on GitHub.
Already have an account?
# to comment
引言
上一节介绍了高阶函数的定义,并结合实例说明了使用高阶函数和不使用高阶函数的情况。后面几部分将结合实际应用场景介绍高阶函数的应用,本节先来聊聊函数柯里化,通过介绍其定义、比较常见的三种柯里化应用、并在最后实现一个通用的 currying 函数,带你认识完整的函数柯里化。
有什么想法或者意见都可以在评论区留言,下图是本文的思维导图,高清思维导图和更多文章请看我的 Github。
柯里化
定义
函数柯里化又叫部分求值,维基百科中对柯里化 (Currying) 的定义为:
用大白话来说就是只传递给函数一部分参数来调用它,让它返回一个新函数去处理剩下的参数。使用一个简单的例子来介绍下,最常用的就是 add 函数了。
实际应用
1、延迟计算
我们看下面的部分求和例子,很好的说明了延迟计算这个情况。
上面的代码理解起来很容易,就是「用闭包把传入参数保存起来,当传入参数的数量足够执行函数时,就开始执行函数」。上面的 currying 函数是一种简化写法,判断传入的参数长度是否为 0,若为 0 执行函数,否则收集参数。
另一种常见的应用是 bind 函数,我们看下 bind 的使用。
这里
bind
用来改变函数执行时候的上下文,但是函数本身并不执行,所以本质上是延迟计算,这一点和call / apply
直接执行有所不同。我们看下
bind
模拟实现,其本身就是一种柯里化,我们在最后的实现部分会发现,bind 的模拟实现和柯理化函数的实现,其核心代码都是一致的。以下实现方案是简化版实现,完整版实现过程和代码解读请看我之前写的一篇文章,【进阶3-4期】深度解析bind原理、使用场景及模拟实现。
2、动态创建函数
有一种典型的应用情景是这样的,每次调用函数都需要进行一次判断,但其实第一次判断计算之后,后续调用并不需要再次判断,这种情况下就非常适合使用柯里化方案来处理。即第一次判断之后,动态创建一个新函数用于处理后续传入的参数,并返回这个新函数。当然也可以使用惰性函数来处理,本例最后一个方案会有所介绍。
我们看下面的这个例子,在 DOM 中添加事件时需要兼容现代浏览器和 IE 浏览器(IE < 9),方法就是对浏览器环境进行判断,看浏览器是否支持,简化写法如下。
但是这种写法有一个问题,就是每次添加事件都会调用做一次判断,那么有没有什么办法只判断一次呢,可以利用闭包和立即调用函数表达式(IIFE)来处理。
上面这种实现方案就是一种典型的柯里化应用,在第一次的
if...else if...
判断之后完成部分计算,动态创建新的函数用于处理后续传入的参数,这样做的好处就是之后调用就不需要再次计算了。当然可以使用惰性函数来实现这一功能,原理很简单,就是重写函数。
第一次调用
addEvent
函数后,会进行一次环境判断,在这之后addEvent
函数被重写,所以下次调用时就不会再次判断环境,可以说很完美了。3、参数复用
我们知道调用
toString()
可以获取每个对象的类型,但是不同对象的toString()
有不同的实现,所以需要通过Object.prototype.toString()
来获取Object
上的实现,同时以call() / apply()
的形式来调用,并传递要检查的对象作为第一个参数,例如下面这个例子。但是上面方案有一个问题,那就是每种类型都需要定义一个方法,这里我们可以使用 bind 来扩展,优点是可以直接使用改造后的
toStr
。上面例子首先使用
Function.prototype.call
函数指定一个this
值,然后.bind
返回一个新的函数,始终将Object.prototype.toString
设置为传入参数,其实等价于Object.prototype.toString.call()
。实现 currying 函数
我们可以理解所谓的柯里化函数,就是封装「一系列的处理步骤」,通过闭包将参数集中起来计算,最后再把需要处理的参数传进去。那如何实现 currying 函数呢?
实现原理就是「用闭包把传入参数保存起来,当传入参数的数量足够执行函数时,就开始执行函数」。上面延迟计算部分已经实现了一个简化版的 currying 函数。
下面我们来实现一个更加健壮的的 currying 函数。
注释 1:第一次调用获取函数 fn 参数的长度,后续调用获取 fn 剩余参数的长度
注释 2:currying 包裹之后返回一个新函数,接收参数为
...args
注释 3:新函数接收的参数长度是否大于等于 fn 剩余参数需要接收的长度
注释 4:满足要求,执行 fn 函数,传入新函数的参数
注释 5:不满足要求,递归 currying 函数,新的 fn 为
bind
返回的新函数(bind
绑定了...args
参数,未执行),新的 length 为 fn 剩余参数的长度上面使用的是 ES5 和 ES6 的混合语法,那我不想使用
call/apply/bind
这些方法呢,自然是可以的,看下面的 ES6 极简写法,更加简洁也更加易懂。如果你还无法理解,看完下面例子你就更加容易理解了,要求实现一个 add 方法,需要满足如下预期。
我们可以看到,计算结果就是所有参数的和,如果我们分两次调用时
add(1)(2)
,可以写出如下代码。add 方法第一次调用后返回一个新函数,通过闭包保存之前的参数,第二次调用时满足参数长度要求然后执行函数。
如果分三次调用时
add(1)(2)(3)
,可以写出如下代码。前面两次调用每次返回一个新函数,第三次调用后满足参数长度要求然后执行函数。
这时候我们再来看 currying 实现函数,其实就是判断当前参数长度够不够,参数够了就立马执行,不够就返回一个新函数,这个新函数并不执行,并且通过
bind
或者闭包保存之前传入的参数。扩展:函数参数 length
函数 currying 的实现中,使用了
fn.length
来表示函数参数的个数,那fn.length
表示函数的所有参数个数吗?并不是。函数的 length 属性获取的是形参的个数,但是形参的数量不包括剩余参数个数,而且仅包括第一个具有默认值之前的参数个数,看下面的例子。
所以在柯里化的场景中,不建议使用 ES6 的函数参数默认值。
我们期望函数 fn 输出
[1, 2, 3]
,但是实际上调用柯里化函数时((a = 1, b, c) => {}).length === 0
,所以调用fn()
时就已经执行并输出了[1, undefined, undefined]
,而不是理想中的返回闭包函数,所以后续调用fn()(2)(3)
将会报错。小结
我们通过定义认识了什么是柯里化函数,并且介绍了三种实际的应用场景:延迟计算、动态创建函数、参数复用,然后实现了强大的通用化 currying 函数,不过更像是柯里化 (currying) 和偏函数 (partial application) 的综合应用,并且在最后介绍了函数的 length,算是意外之喜。
Function.prototype.call.bind(Object.prototype.toString)
参考资料
文章穿梭机
交流
进阶系列文章汇总如下,觉得不错点个Star,欢迎 加群 互相学习。
我是木易杨,公众号「高级前端进阶」作者,跟着我每周重点攻克一个前端面试重难点。接下来让我带你走进高级前端的世界,在进阶的路上,共勉!
The text was updated successfully, but these errors were encountered: