函数式编程
一、前置知识
-
函数式编程的本质:
面向对象编程是对一个事物的抽象,形成一个对象;
函数式编程主要是把某一种输入到输出的过程进行抽象,例如数组的filter函数,即更加注重输入和输出的映射关系。
-
函数是一等公民
函数是一等公民的意思就是说函数可以和变量一样作为最基本的元素,即函数可以作为调用的传参 或 返回值 此时,这两种情况下的函数就是常说的高阶函数!
-
什么是高阶函数
高阶函数的作用:可以用来屏蔽通用的细节,用户只需要知道想要的目标和对应解决这类问题的函数(例如:fliter, some, every等),进而解决通用的抽象问题。
常见的高阶函数: filter, every, some, any, find, sort
二、纯函数
特点: 1. 给相同的输入,得到的输出是相同的; 2. 不会对外部产生副作用。
什么是副作用?
-
函数会给外部带来状态变化,即:函数除了return一个值外,还会对函数外部带来能够观察到的影响。例如: 发送网络请求;对DOM进行操作;改变state变量;修改全局变量;执行IO变更操作。
回到我们要讨论的纯函数上。纯函数之所以纯,是指这个函数里面只有一个变量---参数变量,函数内部没有不确定的变量(如组件内其他的状态变量放在函数的内部)。这样才能保证相同的输入得到的是相同的输出。
例如:如果函数的输出是: return a < this.status 这个时候就不纯了,而如果改为 return a < 2,这个时候就不受外界的变量影响了。这样纯函数的返回值可以给一个变量缓存,可提供缓存,也是纯函数的好处之一。
三、函数柯里化
参考:https://juejin.im/post/6844903603266650125
currying: 把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数和返回结果的新函数。(还有一种解释:只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数。)
下面看一个例子,函数柯里化实现参数相加。
一个一般的相加函数:
function add (x, y) { return (x + y); }
按照前面currying的定义,我们可以得到
function curriedAdd (x) { return function(y) { return x + y; } }
调用:
curriedAdd(1)(3) === 4 // true var increment = curriedAdd(1) increment(2) === 3 // true var addTen = curriedAdd(10) addTen(2) === 12 // true
对curriedAdd函数进行初步抽象:
function currying(fn, ...args1) { return function(...args2) { return fn(...args1, ...args2); } }
调用:
var increment = currying(add, 1) increment(2) === 3
在初步抽象的currying函数中,currying 函数的返回值其实是一个接收剩余参数并且立即返回计算值的函数。即它的返回值并没有自动被 Currying化 。所以我们可以通过递归来将 currying 的返回的函数也自动 Currying 化。
function trueCurrying(fn, ...args) { if (args.length >= fn.length) { return fn(...args) } return function (...args2) { return trueCurrying(fn, ...args, ...args2) } }
比较多次接受的参数总数与函数定义时的入参数量,当接受参数的数量大于或等于被 Currying 函数的传入参数数量时,就返回计算结果,否则返回一个继续接受参数的函数。
3.2 currying应用场景
3.2.1 参数复用
例如,想要实现一个构建URL协议的函数,同时对于相同域名下的URL, 减少参数的重复传递。
const _ = require("lodash");
function simpleURL(protocol, domain, path) {
return protocol + "://" + domain + "/" + path;
}
const simpleURL1 = _.curry(simpleURL);
const myDomain= simpleURL1('https')('i.cnblog.com);
const url_1 = myDomain('articles/29487593.html');
const url_2 = myDomain('articles/test.html');
3.2.2 延迟执行
延迟执行也是 Currying 的一个重要使用场景,同样 bind 和箭头函数也能实现同样的功能。
1. 通过bind方法
<div onClick={handleClick.bind(null, data)} />
与currying相比,bind 方法需要强制绑定 context,也就是 bind 的第一个参数会作为原函数运行时的 this 指向。而 currying 不需要此参数。
2. 箭头函数
<div onClick={() => this.handleClick(data)} />
箭头函数能够实现延迟执行,同时也不像 bind 方法必需指定 context。可能唯一需要顾虑的就是在 react 中,会有人反对在 jsx 标签内写箭头函数,这样子容易导致直接在 jsx 标签内写业务逻辑。
3. currying
<div onClick={currying(handleClick, data)} />

性能从高到低结果:箭头函数>bind>currying>trueCurrying
currying 函数相比 bind 函数,其原理相似,但是性能相差巨大,其原因是 bind 由浏览器实现,运行效率有加成。
从这个结果看 Currying 性能无疑是最差的,但是另一方面就算最差的 trueCurrying 的实现,也能在个人电脑上达到 50w Ops/s 的情况下,说明这些性能是无需在意的。
而 trueCurrying 方法中实现的自动 Currying 化,是另外三个方法所不具备的。
所以,除非一定要使用函数式编程,可以使用bind或者箭头函数来替代curring.

浙公网安备 33010602011771号