You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
In computer science, partial application (or partial function application) refers to the process of fixing a number of arguments to a function, producing another function of smaller arity.
前言
这节课属于 JavaScript 中函数的高级应用。随着 React/Redux 的火热,函数式编程也逐渐被带入了前端的应用领域,甚至还诞生了 elm、ClojureScript 等基于 JavaScript 的函数式语言。熟练掌握这节课的内容,对后续学习函数式编程会有一定帮助。
1. 高阶函数
高阶函数也是函数式编程中的一个概念,使用范围比较广泛。在现在很火的 React 中,高阶组件就是基于高阶函数发展而来。
先看一下高阶函数的定义:
举一个简单的例子:
这个
add
函数就是一个高阶函数,它接收了另一个f
函数。而在 ES5 中出现的
forEach
、map
、some
、every
等函数也属于高阶函数,他们都接收了一个匿名函数作为参数:2. 偏函数
下面是维基百科对偏函数 (Partial application) 的定义:
翻译一下,意思就是在计算机科学中,部分应用程序(或者部分功能应用程序)是指固定一个函数的一些参数,然后产生另一个更小元的函数。
那么什么是元呢?元就是函数参数的个数,比如带有两个参数的函数被称为二元函数。
偏函数是函数式编程中的一部分,使用偏函数可以冻结那些预先确定的参数来缓存函数参数。在运行的时候,当获得需要的剩余参数后,可以将他们解冻,传递到最终的参数中,从而使用最终确定的所有参数去调用函数。
简单来说就是,把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单。
举个比较简单的例子,下面的
sum_add_1
就是一个偏函数。那么怎么实现这个
partial
方法呢?实际上使用原生的bind
方法就能产生一个偏函数。可是
bind
函数中一般需要传入上下文给第一个参数,我们这里可以实现一个无关上下文的partial
函数。由于
partial
函数执行后返回了一个新的函数,那么它一定是个高阶函数。可以考虑如下实现:3. 柯里化
在 JS 的函数式编程中,柯里化是一个很重要的概念,这个概念在我们实际开发中也经常会用到。
3.1 定义
函数柯里化的意思就是你可以一次传很多参数给
curry
函数,也可以分多次传递,curry
函数每次都会返回一个函数去处理剩下的参数,一直到返回最后的结果。这里还是举几个例子来说明一下:
3.2 柯里化求和函数
这里每次传入参数都会返回一个新的函数,这样一直执行到最后一次返回 a+b+c 的值。
但是这种实现还是有问题的,这里只有三个参数,如果哪天产品经理告诉我们需要改成100次?我们就重新写100次?这很明显不符合开闭原则,所以我们需要对函数进行一次修改。
我们通过判断下一次是否传进来参数来决定函数是否运行,如果继续传进了参数,那我们继续把参数都保存起来,等运行的时候全部一次性运行,这样我们就初步完成了一个柯里化的函数。
3.3 通用柯里化函数
这里只是一个求和的函数,如果换成求乘积呢?我们是不是又需要重新写一遍?仔细观察一下我们的
add
函数,如果我们将if里面的代码换成一个函数执行代码,是不是就可以变成一个通用函数了?在之前的方法上面,我们进行了扩展,这样我们就已经实现了一个比较通用的柯里化函数了。
也许你想问,我不想每次都使用那个丑陋的括号结尾怎么办?
这里根据函数 fn 的参数数量进行判断,直到传入的数量等于 fn 函数需要的参数数量才会返回 fn 函数的最终运行结果,和上面那种方法原理其实是一样的,但是这两种方式都太依赖参数数量了。
我在简书还看到别人的另一种递归实现方法,实现思路和我类似。
这里是对参数个数进行了计算,如果需要无限参数怎么办?比如下面这种场景。
这里主要有一个知识点,那就是函数的隐式转换,涉及到
toString
和valueOf
两个方法,如果直接对函数进行计算,那么会先把函数转换为字符串,之后再参与到计算中,利用这两个方法我们可以对函数进行修改。经过修改,我们的函数最终版是这样的。
那么我们说了那么多,柯里化究竟有什么用呢?
3.4 预加载
在很多场景下,我们需要的函数参数很可能有一部分一样,这个时候再重复写就比较浪费了,我们提前加载好一部分参数,再传入剩下的参数,这里主要是利用了闭包的特性,通过闭包可以保持着原有的作用域。
上面例子中,使用 `hasSpaces 函数来保存正则表达式规则,这样可以有效的实现参数的复用。
3.5 动态创建函数
这个其实也是一种惰性函数的思想,我们可以提前执行判断条件,通过闭包将其保存在有效的作用域中,来看一种我们平时写代码常见的场景。
在这个例子中,我们每次调用
addEvent
的时候都会重新进行if语句进行判断,但是实际上浏览器的条件不可能会变化,你判断一次和判断N次结果都是一样的,所以这个可以将判断条件提前加载。但是这样做还是有一种缺点,因为我们无法判断程序中是否使用了这个方法,但是依然不得不在文件顶部定义一下
addEvent
,这样其实浪费了资源,这里有一种更好的解决方法。在
addEvent
函数里面对其重新赋值,这样既解决了每次运行都要判断的问题,又解决了必须在作用域顶部执行一次造成浪费的问题。4. 反柯里化
上面我们介绍过函数柯里化,从字面意思上来理解,反柯里化恰恰和柯里化相反,是为了扩大适用范围,创建一个应用范围更广的函数。使本来只有特定对象才适用的方法,扩展到更多的对象。
看下面一个例子,我们给函数增加一个反柯里化的方法。
通过反柯里化方法,甚至可以让对象使用数组的
push
方法:但是直接在函数原型上面修改不太好,这里可以实现一个更加通用的反柯里化方法。
使用方法和原来的类似:
简单理解,柯里化就是对高阶函数进行降阶处理,而反柯里化增加反过来扩大使用范围。
反柯里化的好处就是将原本只有
target
能使用的方法借了出来,可以给更多对象来使用。我们在开发中,经常会借用
Object.prototype.toString
来检测一个变量的类型,这也是反柯里化的用法之一。5. 推荐阅读
The text was updated successfully, but these errors were encountered: