目录
如果有小伙伴想要回顾之前的知识的话,点击下面的链接查看之前所有的学习栏目👇:
《大前端学习专栏:目录》
概念
纯函数:相同的输入永远会得到相同的输出,且没有任何可观察的副作用。
如:数组的slice
是一个纯函数,一个数组无论调用几次,都会返回相同的结果;但是splice
是一个非纯函数,每次调用后都会改变原数组,导致下一次调用会得到不同的结果。
纯函数的优点
由于纯函数有传入相同参数会得到相同的输出的特点,我们可以对一些复杂函数的执行结果进行缓存,提升性能,以下是Vue2.0源码中对纯函数缓存的实现:
/**
* Create a cached version of a pure function.
*/
export function cached<F: Function> (fn: F): F {
const cache = Object.create(null)
return (function cachedFn (str: string) {
const hit = cache[str]
return hit || (cache[str] = fn(str))
}: any)
}
const cachedHelloFn = cached((str)=> console.log(str))
cachedHelloFn();
cachedHelloFn(); //有缓存
内部函数cachedFn
牵引cache
对象,首先检查cache
中是否有缓存,如果没有缓存就将fn
的调用结果缓存,并返回缓存值,第二次调用后就会从缓存中获取值。
副作用
在提到函数柯里化之前,我们还是需要提一提,函数的副作用。什么是函数的副作用?我们通过以下这个例子来简单的理解以下:
//一个副作用函数
let adultAge = 18;
function checkAge(age){
return age >= adultAge;
}
console.log(checkAge(15)); //false
console.log(checkAge(15)); //false
首先,上面的这个函数是纯函数吗?有的同学可能会说是,毕竟你不管传入多少次相同的age
,都是会返回相同的结果,所以会认为这是一个纯函数。但是,如果我这样一下:
//一个副作用函数
let adultAge = 18;
function checkAge(age){
return age >= adultAge;
}
console.log(checkAge(15)); //false
adultAge = 14;
console.log(checkAge(15)); //true
那这样你认为checkAge
还是一个纯函数吗?显然不是。
记着纯函数的概念是:纯函数:相同的输入永远会得到相同的输出,且没有任何可观察的副作用。
那以上这个函数就是一个副作用函数,这个函数依赖于函数外部的变量,一旦外部的变量发生变化,这个函数就会产生副作用。
副作用的来源:
- 数据库
- 配置文件
- 用户的输入
- …
所有的外部交互都有可能会发生副作用,副作用会给程序带来不稳定性和安全隐患,但是副作用又是不可避免的,尽可能的在它们的可控范围内发生。
那怎么样修改一下这个checkAge
才能变成一个纯函数呢?有的同学可能会这样去改:
function checkAge(age){
let adultAge = 18;
return age >= adultAge;
}
这样的确可以,但是这里会有一个硬编码let adultAge = 18
,我们知道在编程中需要尽量地去避免使用硬编码。那这里就需要使用一个叫做函数柯里化的技术了。
函数柯里化
柯里化(Currying):当一个函数有多个参数时,先传递一部分参数调用它(这部分参数永远不变),返回一个新的函数传递剩余参数。
知道了柯里化的概念之后,我们来手动改造一下上面的例子:
function makeCheckAgeFn(adultAge) {
return function (age) {
return age >= adultAge;
};
}
//ES6写法
//const makeCheckAgeFn = adultAge => (age => age >= adultAge);
let checkAge = makeCheckAgeFn(18);
console.log(checkAge(29)); //true
console.log(checkAge(10)); //false
checkAge = makeCheckAgeFn(30);
console.log(checkAge(29)); //false
console.log(checkAge(30)); //true
上面这个函数,将adultAge
放在了闭包结构中,所以内部函数会一直拥有这个变量,且这样也消除了硬编码的问题。当然,如果你熟悉ES6语法的话,只需要一行就能实现上面这个柯里化过程了。
Lodash中的柯里化函数
如果平时大家使用过Lodash这个函数库的话,你们应该知道Lodash有一个curry
方法,能将一个函数变为柯里化函数:
const _ = require('lodash');
const match = _.curry((reg, str) => str.match(reg));
调用lodash.curry
来柯里化一个字符串匹配的函数,那怎么使用这个函数呢?
const matchSpace = match(/\s+/g);
console.log(matchSpace('abc 123'));
console.log(matchSpace('123 abc'));
这样就可以使用了,这里巧妙的运用了函数的柯里化过程,将正则表达式/\s+/g
作为一个不变的量,去重复的使用它。
而这里,我们发现Lodash的curry
非常好用,并且它是一个通用的柯里化方法,不像我们刚刚写的柯里化只是针对checkAge
这个函数的柯里化。那么Lodash是怎么做到的呢?
原理
以下是本人对于Lodash的curry
函数的模拟:
function curry(fn) {
return function curried(...args) {
//如果实参形参个数相同,就直接调用该函数
if (args.length < fn.length) {
return function () {
//拼接剩余参数,arguments是类数组对象,需要转换
return fn(...args.concat(Array.from(arguments)));
};
}
return fn(...args);
};
}
函数柯里化的总结
- 函数柯里化可以先给一个函数传递较少的参数来获得一个已经记住这些参数的函数。
- 函数柯里化是对函数的一种缓存
- 让函数变得更加灵活,使函数的粒度更小
- 可以把多元函数(多个参数)转换成一元函数(一个参数),方便组合其他函数(函数的组合我们在下一篇中会详细讲解)