什么是函数式编程?
主要的编程范式有三种:命令式编程、声明式编程和函数式编程
函数式编程更加强调程序执行的结果而非执行的过程,倡导利用若干简单的执行单元让计算结果不断渐进,逐层推导的运算,而非设计一个复杂的执行过程。简单的来说,就是把过程逻辑变成函数,定义好输入参数,只关心它的输出结果。
优点:
- 更好的状态管理:因为它的宗旨是无状态,或者更少的状态,能最大化的减少这些未知、优化代码、减少出错情况
- 更简单的复用:固定输入 -> 固定输出,没有其他外部变量影响,并且无副作用。这样代码复用时,完全不需要考虑它的内部实现和外部影响
- 更加优雅的组合:一个函数也可能有由多个小函数组成的。更强的复用性,带来更强大的组合性
- 减少代码量,提高维护性
缺点:
-
性能:函数式编程相对于指令式编程,性能决定是一个短板,应该它往往会对一个方法进行过度包装,从而产生上下文切换的性能开销
-
资源占用:在 JS 中为了实现对象的状态的不可变,往往会创建新的对象。因此,它对垃圾回收所产生的压力远远超过其他编程方式
-
递归陷阱
纯函数
纯函数,无副作用的函数,是对给定的输入返回相同输出的函数,并且要求你所有的数据都是不可变的,即纯函数 = 无状态 + 数据不可变。
特点:
- 函数内部传入的指定值,就会返回确定唯一的值
- 不会造成超出作用域的变化,例如修改全局变量或者引用传递的参数
优势:
- 不依赖外部环境计算,不会产生副作用,提高函数的复用性
- 可读性更强,函数不管是否纯函数,都会有一个语义化的名称,更便于阅读
- 可以组装成复杂任务的可能性,符合模块化概念及单一职责原则
高阶函数
以函数作为输入或者输出的函数称为高阶函数。
const forEach = function(arr,fn){
for(let i = 0;i < arr.length; i++){
fn(arr[i])
}
}
let arr = [1,2,3]
forEach(arr,(item)=>{
console.log(item)
})
// 高阶函数存在缓存的特性,主要是利用了闭包
const once = (fn) => {
let done = false
return function () {
if (!done) {
fn.apply(this, fn)
} else {
console.log('该函数已经执行')
}
done = true
}
}
let hello = once(()=>{
console.log('hello')
})
hello() // hello
hello() // 该函数已经执行
柯里化
柯里化是把一个多参数函数转化成一个嵌套的一元函数过程
意义:
- 让纯函数更纯,每次接收一个参数,松散解耦
- 惰性执行(函数只有在需要的时候执行,即不产生无意义的中间变量)
// 二元函数
// let fn = (x,y) => x + y
// console.log(fn(1,2))
// 转成柯里化函数(二元)
// const curry = function(fn){
// return function(x){
// return function(y){
// return fn(x,y)
// }
// }
// }
// let curryFn = curry(fn)
// console.log(curryFn(1)(2))
// 多参数柯里化
const curry = function (fn) {
return function curriedFn(...args) {
// 递归收集参数
// console.log(args)
if (args.length < fn.length) {
return function () {
return curriedFn(...args.concat([...arguments]))
}
}
return fn(...args)
}
}
const fn = (a, b, x, y, z) => a + b + x + y + z
const curryFn = curry(fn)
console.log(curryFn(1)(2)(3)(4)(5))
组合与管道
组合函数,目的是将多个函数组合成一个函数
意义:把很多个小函数组合起来完成更加复杂的逻辑
function afn(a) {
return a * 2
}
function bfn(b) {
return b * 3
}
const compose = (a, b) => c => a(b(c))
let myFn = compose(afn, bfn)
console.log(myFn(2))
// 组合函数 (从右往左执行)
const compose2 = (...fns) => val => fns.reverse().reduce((acc, fn) => fn(acc), val)
let myFn = compose2(afn, bfn)
console.log(myFn(2))
// 管道函数 (从左往右执行)
const pipe = (...fns) => val => fns.reduce((acc, fn) => fn(acc), val)
let pipeFn = pipe(afn,bfn)
console.log(pipeFn(2))
函数缓存
函数缓存,就是将函数运算过的结果进行缓存
本质上就是利用空间(缓存存储)换时间(计算过程)
应用场景:
- 对于昂贵的函数调用,执行复杂计算的函数
- 对于具有有限且高度重复输入范围的函数
- 对于具有重复输入值的递归函数
- 对于纯函数,即每次使用特定输入调用时返回相同输出的函数