函数式编程
认识一下
- 是编程范式之一(常见还有面向过程、面向对象编程(特性:封装、继承、多态))。
- 是对运算过程进行抽象,解决事物和事物之间联系的问题。
是前端编程思维方式的改变
,前端学习函数式编程的原因(主要体现在流行框架和便捷性):- 函数式编程在React和Vue3中得到普及
- Vue2中也使用了大量的高阶函数(高阶函数是函数式编程的特性)
- 函数式编程抛弃了this
- 打包过程中可以更好地利用 tree shaking 过滤无用代码
- 方便测试、方便并行处理
- lodash、underscore、ramda等库,提供了函数式开发的辅助功能
特性
- 函数式编程中的函数指数学中的函数,表示 一种映射关系
- 一定有输入和输出,输入与输出存在强逻辑关系
- 实现了函数的细粒度封装、复用
- 不会保留计算中间的结果,因此变量不可变
扩展了解
函数是一等公民(或头等函数)
js中,函数是一个普通对象
- 函数可以是变量
let functionA = function funcA () {
console.log("hello");
}
const object = {
// 注意:赋值的是方法本身
objectFunc: functionA
}
object.objectFunc()
- 函数可以是参数
let functionB = function funcA () {
console.log("hello");
}
function (functionB) {
for (i = 0; i < 10; i ++) {
functionB()
}
}
- 函数可以作为返回值
// 返回值简单实现
function functionC() {
return function () {
console.log("hello");
}
}
functionC()()
// 应用
// 单例:once函数,fn只会执行一次
function once(fn) {
let isDone = false
return function () {
if (!isDone) {
isDone = true
return fn.apply(this, arguments)
}
}
}
let pay = once(function (money) {
console.log(`${money} 元`);
})
pay(5)
pay(5)
高阶函数
常用高阶函数:
- 数组:
- map(返回一个新的数组,数组中的元素为原始数组调用函数处理后的值)
- every(循环判断数组每个元素)
- some(循环遍历,直到有一个元素满足条件)
- filter(循环遍历,筛选所有满足条件的元素)
通用遍历高阶函数:
- each(通用遍历方法,可用于遍历对象和数组)
回调函数拥有两个参数:第一个为对象的成员或数组的索引,第二个为对应变量或内容。如果需要退出 each 循环可使回调函数返回 false,其它返回值将被忽略。
闭包
- 函数和其周围的状态(语法环境)的引用捆绑形成闭包
- 可在另一个作用域中调用一个函数内部函数并访问到函数的作用域中的成员
- 闭包延长了其内部成员的生命周期
闭包本质
- 函数在执行时,会放到执行栈上,执行完毕,会从执行栈移除,其内部成员变量也会被移除
- 但堆上的作用域成员,由于被外部引用不能释放,因此内部函数依然可以访问外部函数成员
function makeSalary(baseSalary) {
return function (performance) {
return (baseSalary + performance)
}
}
let p1 = makeSalary(100000)
let p2 = makeSalary(120000)
let person1 = p1(2000)
let person2 = p2(3000)
let person3 = p2(3000)
console.log(person1, person2, person3);
// 102000 123000 123000
纯函数
相同的输入永远会得到相同的输出。不会有副作用(即让函数变成非)
优势
- 由于输入始终得到相同结果
- 可以把函数执行结果缓存起来,提高执行速率
- 所有的纯函数都可测试
- 便于并行处理(多线程环境下,纯函数不需要访问共享内存数据,无并发冲突)
- es6以后新增了webworker,可以新增线程
- 纯函数只依赖于参数,是一个封闭的内存空间
// slice是纯函数,调用多次的结果相同
let arr = [1,2,2,4,5]
arr.slice(0,2)
arr.slice(0,2)
arr.slice(0,2)
// splice是非纯函数,调用多次的结果相同
arr.splice(0,2)
arr.splice(0,2)
arr.splice(0,2)
lodash
现代的JS实用函数库,提供了模块化,高性能等功能。lodash中的FP模块是纯函数模块
- 常用的函数
- 数组相关函数
- first(别名:head)
- last
- tpUpper
- reverse
- each(别名:forEach)
arr.each((item, index) => {})
- includes
- find
- findIndex
- 数组相关函数
柯里化
示例:
// 普通写法
function chackNumber(baseNumber) {
return function (number) {
return number > baseNumber
}
}
// es6写法
let chackNumber = baseNumber => (number => number > baseNumber)
let largeThan10 = chackNumber(10)
let largeThan20 = chackNumber(20)
largeThan10(20) // true
largeThan10(9) // false
largeThan20(30) // true
largeThan20(18) // false
先传入部分参数,得到新的函数,向新的函数传入剩余参数,得到结果,这个过程就是 函数柯里化
优势
- 实现了
多元函数
转一元函数
- 可以让代码结构、执行流程 更加清晰
- 让函数更灵活,粒度更小
- 可以得到保留部分固定参数的新函数
lodash中的柯里化
const _ = require("lodash")
function getSum(a, b, c){
return a + b + c
}
let curried = _.curry(getSum)
// 三元函数
curried(1, 2, 3)
// 转 一元函数
curried(1) (2) (3)
// 转 一元 + 二元函数
curried(1)(2, 3)
_.curry(func)
- 创建一个函数,接受一个或多个func的参数
- func需要的的参数都被提供,则执行func并返回
- 参数:需要柯里化的函数
- 返回值:柯里化后的函数
实践
lodash.curry 正则表达式应用
let _ = require("lodash")
const match = _.curry(function (reg, str) {
return str.match(reg)
})
const haceSpace = match(/\s+/g)
const haveNumber = match(/\d+/g)
const filter = _.curry(function (func, array) {
return array.filter(func)
})
const findSpace = filter(haceSpace)
const findNumber = filter(haveNumber)
console.log(findSpace(["John weak","Tom_Jackson"]))
console.log(findNumber(["John weak","Tom_Jackson","baby 5"]))
函数组合
应用场景
解决函数过长,导致bug不容易暴露的问题
实现方式
- 将函数fn拆分成fn1, fn2, fn3,顺序执行后的结果,与执行fn的结果相同
- fn2的输入是fn1的输出,fn3的输入是fn2的输出
- 函数组合要满足函数组合律
compose
let result = compose(f,g,h)
let newResult = compose(func(f,g),h) = compose(f,func(g,h))
flowRight
let _ = require("lodash")
const reverse = arr => arr.reverse()
const first = arr => arr[0]
const toUpper = s => s.toUpperCase()
// 从右到左依次执行,前一个函数执行结果,会作为后一个函数的参数
const f = _.flowRight(toUpper,first,reverse)
console.log(f(['one','two','three'])); // THREE