-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in 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))
}
} |
太难了 |
看不懂啊 被自己蠢死 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in 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: