纯函数概念
相同的输入永远都是相同的输出,并且没有任何可观察的副作用(什么是副作用后面会讲到)
纯函数就类似于数学中的函数(用来描述输入和输出之间的关系)y=f(x)
lodash就是一个纯函数的功能库,提供了对数组,数字,字符串,对象,函数等一连串的操作方法
举个例子:splice和slice
- slice返回数组中的指定部分,不改变原来的数组,是纯函数
- splice对数组进行操作并且返回此数组,会改变原来的数组,是不纯的函数
// 纯函数slice,不纯的函数splice
let array = [1, 2, 3, 4, 5];
// 三次执行都是一样的返回值 [1,2,3]
console.log(array.slice(0, 3));
console.log(array.slice(0, 3));
console.log(array.slice(0, 3));
let spliceArr = [1, 2, 3, 4, 5, 6, 7, 8];
// 三次执行之后的每次结果都不一样
// [1,2] [3,4] [5,6]
console.log(spliceArr.splice(0, 2));
console.log(spliceArr.splice(0, 2));
console.log(spliceArr.splice(0, 2));
- 函数式编程不会保留计算中间的结果,所以变量是不可变的(没有状态的)
- 我们可以把一个函数的执行结果交给另一个函数处理
Lodash:纯函数代表
lodash
这里先简单的提一下这个工具,后面讲解柯里化的时候会再次提及
纯函数的好处
1.可缓存
因为纯函数的特性:相同的输入始终有相同的输出,所以可以把纯函数的结果缓存起来
const _ = require("lodash");
const getCircleArea = (r) => {
console.log(r);
return Math.PI * r * r;
};
let getAreaWithMemory = _.memoize(getCircleArea);
console.log(getAreaWithMemory(4));
console.log(getAreaWithMemory(4));
console.log(getAreaWithMemory(4));
这段代码中,在getCircleArea中打印了一个r,之后的返回结果其实显而易见的是:
为什么会这样?因为第一次执行之后数据已经丢入了缓存中,再次执行相同的输入,是直接从缓存中拿取结果。这个演示代码太过于简单,所以直观上来说用这种办法演示是最有效的
模拟memoize:
function memoize(fn) {
// 存储执行结果
let cache = {};
return function () {
let key = JSON.stringify(arguments);
cache[key] = cache[key] || fn.apply(fn, arguments);
return cache[key];
};
}
const getCircleArea = (r) => {
console.log(r);
return Math.PI * r * r;
};
let getAreaWithMemory = memoize(getCircleArea);
console.log(getAreaWithMemory(4));
console.log(getAreaWithMemory(4));
console.log(getAreaWithMemory(4));
上述代码,工作原理如下:
- 创建一个变量
cache
用于存储函数执行结果 - 返回一个新的函数,该函数会先将传入的参数使用
JSON.stringify
方法转为字符串类型,并作为 key 存储在cache
对象中 - 如果
cache
中已经存在相同的 key,则直接返回缓存的结果;否则调用原始函数fn
执行并将结果存储到cache
中再返回
这样,在多次调用函数时,如果传入相同的参数,则从缓存中获取结果返回,避免了重复计算,提高了函数的执行效率。
2.可测试
纯函数让测试更加的方便
3.方便并行处理
- 多线程环境下,并行操作共享的内存数据很可能会出现以外的情况
- 纯函数不需要访问共享的内存数据,所以在并行环境下可以任意的运行纯函数(web worker可以开启多线程)
副作用
回顾一下纯函数的定义:对于相同的输入永远都有着相同的输出,并且不会有任何可观察的副作用
// 不纯的
let miniAge = 18;
function checkAge (age) {
return age >= miniAge
}
// 纯函数(有硬编码,柯里化可以解决)
function checkAgeClean (age) {
let miniAgeClean = 18;
return age >= miniAgeClean
}
Tips:什么叫硬编码?就是有具体的值,这在编程里是极其糟糕的。
副作用让一个函数变的不纯(如上例),纯函数的根据相同的输入返回相同的输出,如果函数依赖于外部的状态就无法保证输出相同,就会带来副作用。
副作用的来源:
- 获取用户输入
- 数据库
- 配置文件啊
- eg....
所有的外部交互都有可能带来副作用,副作用也使得方法通用性下降不适合扩展和可重用性,同时副作用会给程序中带来安全隐患给程序带来不确定性,但是副作用不可能完全禁止,尽可能控制它们在可控范围内发生。
柯里化(Haskell Brooks Curry)
上面的代码里提到了如何解决硬编码的方法,就是柯里化
// 柯里化解决硬编码
function checkAge(age) {
let miniAge = 18;
return age >= miniAge;
}
// 普通的纯函数
function cleanCheckAge(miniAge, age) {
return age >= miniAge;
}
// 柯里化
function checkAgeCurry(min) {
return function (age) {
return age >= min;
};
}
let checkAge18 = checkAgeCurry(18);
console.log(checkAge18(20));
// ES6写法
const checkAgeCurryES6 = (min) => (age) => age >= min;
柯里化:
- 当一个函数有多个参数的时候先传递一部分参数调用它(这部分的参数以后永远不变)
- 然后返回一个新的函数接收剩下的参数,返回结果