什么是纯函数?
相同的输入永远会得到相同的输出,而且没有任何观察的副作用 纯函数就类似数学中的函数(用来描述输入和输出之间的关系),y=f(x)
纯函数和不是纯函数的对比
//纯函数 slice 返回数组中的指定部分,不会改变原数组,
//不是纯函数 splice 对数组进行操作返回该数组,会改变原数组
let array = [1,3,5,7,9,11,2,4,6,8,10];
console.log(array.slice(0,3));//[ 1, 3, 5 ]
console.log(array.slice(0,3));//[ 1, 3, 5 ]
console.log(array.slice(0,3));//[ 1, 3, 5 ]
console.log(array.splice(0,3));//[ 1, 3, 5 ]
console.log(array.splice(0,3));//[ 7, 9, 11 ]
console.log(array.splice(0,3));//[ 2, 4, 6 ]
纯函数优点
- 可缓存: 因为纯函数对相同的输入始终有相同的结果,所以可以把纯函数的结果缓存起来。
- 可测试: 纯函数让测试更方便
- 并行处理: 在多线程环境下并行操作共享的内存数据很可能会出现以外情况,纯函数不需要访问共享数据,所以在并行环境下可以任意运行纯函数(web worker)。
纯函数的缺点
纯函数的根据相同的输入返回相同的输出,如果函数依赖于外部的状态就无法保证输出相同,就会带来副作用。
lodash库
lodash是一个纯函数的功能库,提供了对数组、数字、对象、字符串、函数等操作的一些方法。
API文档
_.memoize(func, [resolver])
创建一个会缓存 func 结果的函数。 如果提供了 resolver ,就用 resolver 的返回值作为 key 缓存函数的结果。 默认情况下用第一个参数作为缓存的 key。 func 在调用时 this 会绑定在缓存函数上。 注意: 缓存会暴露在缓存函数的 cache 上。 它是可以定制的,只要替换了 _.memoize.Cache 构造函数,或实现了Map 的 delete, get, has, 和 set方法。
const _ = require('lodash')
function getArea(r) {
console.log(r);
return Math.PI * r * r
}
let getAreas = _.memoize(getArea);
console.log(getAreas(4));
console.log(getAreas(4));
console.log(getAreas(4));
//输出结果:
//4
//50.26548245743669
//50.26548245743669
//50.26548245743669
//传入相同的参数就会被缓存
memoize的实现原理
function getArea(r) {
console.log(r);
return Math.PI * r * r
}
function memoize(fn) {
let cache = {}
return function () {
let key = JSON.stringify(arguments)
cache[key] = cache[key] || fn.apply(fn,arguments)
return cache[key]
}
}
let getAreas = memoize(getArea);
console.log(getAreas(5));
console.log(getAreas(5));
console.log(getAreas(5));
//输出结果
//5
//78.53981633974483
//78.53981633974483
//78.53981633974483
使用柯里化解决函数的硬编码副作用
将原本需要三个参数才能运行的函数,转化为,可以先让一个函数去接受一个参数,调用后,再返回去调用剩余其他的参数,直到完成所有参数的调用
//柯里化演示
//1.硬编码
function checkAge(age) {
let min = 18
return age >= min;
}
//2.函数式
function checkAge1(age,min) {
return age >= min;
}
//3.纯函数柯里化方式
function checkAge2(min) {
return function (age) {
return age >= min;
}
}
let check18 = checkAge2(18)
console.log(check18(20));//true
console.log(check18(18));//true
console.log(check18(17));//false
//4.es6箭头函数
const ca = min => (age=>age>=min)
let c20 = ca(20)
console.log(c20(20));//true
console.log(c20(18));//false
console.log(c20(22));//true
_.curry(func, [arity=func.length])
创建一个函数,该函数接收 func 的参数,要么调用func返回的结果,如果 func 所需参数已经提供,则直接返回 func 所执行的结果。或返回一个函数,接受余下的func 参数的函数,可以使用 func.length 强制需要累积的参数个数。
const _ = require('lodash')
function getSum(a,b,c) {
return a+b+c;
}
const curried = _.curry(getSum)
console.log(curried(2,3,4));//9
console.log(curried(2)(3,4));//9
console.log(curried(2,3)(4));//9
柯里化实例
const _ = require('lodash')
const match = _.curry((reg,str)=>str.match(reg))
const haveNumber = match(/\d+/g)
const filter = _.curry((func,arr)=>arr.filter(func))
const findNumber = filter(haveNumber)
//console.log(findNumber(['f12aw','sss']));
//console.log(haveNumber("dhsjahdj23132"));
const findNumbers = (arge)=>{
if (typeof arge == 'string') {
return haveNumber(arge)
}else if(Array.isArray(arge)){
return findNumber(arge)
}
}
console.log(findNumbers('f12aw'));
curry的实现原理
function curry(func) {
return function curried(...args) {
if (args.length < func.length) {
return function () {
return curried(...args.concat(Array.from(arguments)))
}
}
return func(...args)
}
}
柯里化函数的总结:
- 柯里化可以让我们给一个函数传递较少的参数得到一个已经记住了某些固定参数的新函数
- 这是一种对函数参数的‘缓存’
- 让函数变得更灵活,让函数的粒度更小
- 可以把多元函数转换成一元函数,可以组合使用函数产生强大的功能。
函数组合
函数组合(compose):如果一个函数要经过多个函数处理才能得到最终值,这个时候可以把中间过程的函数合并成一个函数。函数组合就是吧这些管道连接起来,让数据穿过多个管道形成最终结果。函数组合默认是从右到左执行。
_.flow([funcs])
创建一个函数。 返回的结果是调用提供函数的结果,this 会绑定到创建函数。 每一个连续调用,传入的参数都是前一个函数返回的结果。 函数的顺序是从左往右的
_.flowRight([funcs])
函数的顺序是从右往左的
const reverse = arr => arr.reverse()
const first = arr => arr[0]
const toUpper = s => s.toUpperCase()
const f = _.flow(reverse,first,toUpper)
console.log(f(['one','two']));//TWO
const fn = _.flowRight(toUpper,first,reverse)
console.log(fn(['one','two']));//TWO