函数式编程
函数式编程是一种编程范式,主要是利用函数把运算过程封装起来,通过组合各种函数来计算结果。
函数式编程的特点
- 函数是“一等公民” 函数与其他数据类型一样,你可以像对待任何其他数据类型一样对待它们——把它们存在数组里,当作参数传递,赋值给变量…等等
// 函数作为参数传递
setTimeout(function() {
console.log(1)
}, 1000)
- 只用“表达式”,不用“语句”(声明式编程) "表达式"(expression)是一个单纯的运算过程,总是有返回值;"语句"(statement)是执行某种操作,没有返回值。函数式编程要求,只使用表达式,不使用语句。也就是说,每一步都是单纯的运算,而且都有返回值。
- 没有副作用
所谓"副作用",指的是函数内部与外部互动,产生运算以外的其他结果。
副作用包含:
- 更改文件系统
- 往数据库插入记录
- 发送一个 http 请求
- 可变数据
- 打印/log
- 获取用户输入
- DOM 查询
- 访问系统状态
- 惰性执行 函数只在需要的时候执行,不产生无意义的中间变量。从头到尾都在写函数,只有在最后的时候才通过调用 产生实际的结果。 如Vue中路由的懒加载:
const List = () => import('@/components/list.vue') //只是定义了一个函数,没有执行import的动作
const router = new VueRouter({
routes: [
{ path: '/list', component: List }
]
})
- 引用透明 指的是函数的运行不依赖于外部变量或"状态",只依赖于输入的参数,任何时候只要参数相同,引用函数所得到的返回值总是相同的。
- 无锁并发
柯里化
把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数。
优点: 函数柯里化之后让函数变得更加单一,一次只接受一个参数,松散解耦,提高了适用性。
缺点: 函数的通用性将变低,比如原来接收3个参数的函数,我们可以拿着3个参数处理更多操作,但是函数变为只接收一个参数后,我们的操作会受很多限制,降低了通用性。
主要使用场景:参数复用、延迟执行。
// 给数组arr中每个元素+1再+5再-2
let arr = [2, 5, 6, 3, 9];
// 正常做法
let arr1 = arr.map(val => {
return val + 1
})
let arr2 = arr1.map(val => {
return val + 5
})
let arr3 = arr2.map(val => {
return val - 2
})
console.log(arr3) // [6, 9, 10, 7, 13]
// 柯里化
const calArr = (num) => {
return (data) => {
return data + num
}
}
// 返回的函数通过闭包即住了第一个传入的参数num
let arr4 = arr.map(calArr(1)).map(calArr(5)).map(calArr(-2))
console.log(arr4) // [6, 9, 10, 7, 13]
实现一个柯里化函数
函数柯里化的重点在于闭包和递归,将每次执行的作用域保存在内存中,等待后续使用
实现思路:
- 拿到原函数的形参个数length。
- 拿到目前接收到的参数args。
- 比较len和args大小。
- 根据大小判断返回一个函数还是返回原函数执行结果。
/**
* @params {Function} fn 原函数
* @params {Array} ...args 可以传入初始参数
*/
function curry(fn, ...args) {
// 返回一个函数
return function () {
// 缓存目前接收到的参数
let _args = [...args, ...arguments];
// 原函数形参个数
let len = fn.length;
// 比较目前的参数累计与原函数应该接收的参数
if (_args.length < len) {
// 代表需要继续返回一个新函数
// 使用递归,形成闭包,保证函数独立,不受影响。
return curry(fn, ..._args);
} else {
// 参数累计够了,执行原函数返回结果
return fn(..._args);
}
}
}
// 一个普通函数
function sum(a, b, c) {
return a + b + c;
}
// 正常调用
console.log(sum(10, 20, 30)); // 60
// 将sum函数柯里化,返回一个新函数
let newFn = curry(sum);
// 新的调用方式
console.log(newFn(10)(20)(30)); // 60
函数组合
函数组合就是将不同功能的函数组合成一个函数。 函数组合要满足结合律:改变函数组合顺序,效果一样 例如:
// 该三种组合模式都是等效的
let t = compose(f, g, h);
compose(compose(f, g), h) === compose(f, compose(g, h)); // true
常见的,使用lodash库来实现组合,
- flow() 是从左到右运行
- flowRight() 是从右到左运行
const _ = require('lodash');
const reverse = array => array.reverse();
const first = array => array[0];
const toUpper = str => str.toUpperCase();
const d = _.flowRight(toUpper, first, reverse);
const b = _.flow(reverse, first, toUpper);
console.log(d(['a', 'b', 'c'])); // C
console.log(b(['a', 'b', 'c'])); // C
###纯函数 纯函数是这样一种函数,即相同的输入,永远会得到相同的输出,而且没有任何可观察的副作用。
- 无副作用
- 必须带参数
- 纯函数不能调用非纯函数
###高阶函数 就是一个函数的参数是函数,或者返回值是函数,满足其中一个就是高阶函数 常用的高阶函数:
- 数组的高阶用法
- forEach()
- filter()
- find/findIndex()
- map()
- some()
- every()
- sort()
- reduce()
- 防抖节流函数 ###函子 参考:阮一峰函数式编程入门教程
函子就是一个特殊的容器,它可以由对象来实现,这个对象中包含了值,这个值永远不会对外公布,有一个map方法,用来操作这个值。还有一个of方法,用来生成一个新的容器。
class Functor {
static of(value){
return new Functor(value)
}
constructor(val){
this._value = val
}
map(fn){
return Functor.of(fn(this._value ))
}
}
//使用的时候
const func = Functor.of.map(x => x+1)
func(1)//Container { _value: 2 }