什么是函数式编程
概念:
函数式编程是一种编程范式,强调使用函数来组合和处理数据。将运算过程抽象成成函数,可以复用
常⻅的编程范式
- 面向过程编程(Procedural Programming)PP:按照步骤 来实现,将程序分解为过程和函数。这些过程和函数按顺序 执行来完成任务。
- 面向对象编程(Object-Oriented Programming)OOP: 将程序分解为对象,每个对象都有自己的状态和行为。
- 面向 对象的核心是(类,实例,继承,封装,多态) 函数式编程(Functional Programming)FP:使用函数来 组合和处理数据,描述数据之间的映射。函数指的并不是编 程语言中的函数,指的是数学意义上的函数 y=f(x) 输入映射 输出 一个函数 f 接收一个参数 x,并根据 x 计算返回一个结果 y。
函数式编程的优势
- 可维护性:函数式编程的程序通常更加简洁和可读,因为它们避免了状态变化和副作用。这使得代码更易于理解和维护。
- 可测试性:由于函数式编程程序通常是无副作用的,所以可以很容易地对其进行单元测试。
- 并发性:函数式编程程序通常是无副作用的,所以可以很容易地并行地执行。
扩展性:函数式编程程序通常是纯函数,可以很容易地组合和重用。
- 可靠性:函数式编程程序通常是无副作用的,所以可以很容易地预测其行为。
- Vue3 也开始拥抱函数式编程,函数式编程可以抛弃 this, 打包过程中更好的利用 tree-shaking 过滤无用的代码
高阶函数
概念
一个函数的参数是一个函数,或者一个函数的返回值是一个 函数。则称这个函数是高阶函数。
类型
函数作为参数
函数作为返回值
纯函数
概念
相同的输入永远会得到相同的输出,而且没有任何的副作用。 (不会对外部环境产生影响,并且不依赖于外部状态)
常⻅副作用:
对全局变量或静态变量的修改
对外部资源的访问(如文件、数据库、网络 http 请求)
对系统状态的修改 (环境变量)
对共享内存的修改
DOM 访问,打印/log 等
副作用使得方法通用性降低,让代码难以理解和预测,测试困 难,导致静态问题等。
lodash 库中所有的方法都是纯函数 方法
柯里化
概念
柯里化是一种函数转换技术,它将一个多参数函数转换为一系 列单参数函数。与之类似的偏函数是指对于一个函数,固定其 中一些参数的值,生成一个新函数,这个新函数接受剩下的参 数
实现
function curry(func){ // 高阶函数
const curried = (...args)=>{
if( args.length < func.length){ // 传递的参数 不满足函数
return (...other)=>curried(...args,...other)
}
return func(...args)
}
return curried
}
const curriedSum = curry(sum)
let curriedSum =curry(sum)
curriedSum(1)(2)(4)
函数组合 compose
概念
如果一个函数要经过多个函数处理才能得到最终的值,这个时候我们可以把中间这些过程函数合并成一个新的函数。
解决的问题
之前
// addPrefix(toFiexd(double(10000))) // 洋葱模型
// double(10000) | toFiexd | addPrefix // 过滤器的用法 管道 (滤网) vue2 filter
现在
flowRight(currency,fixed,double)
避免了层级嵌套,利于函数复用
实现
function flowRight (...fns){
if(fns.length===1){
return fns[1]
}
return fns.reduceRight((a,b)=> (...args)=>b(a(...args)))
//利用自执行函数接口,可以去掉前面和后面的括号,
//代码最终addPrefix(toFiexd(double(1000))),与洋葱模型一致
}
function double (n){
return n*2
}
function fixed (n){
return n.toFixed(2)
}
function currency (n){
return '$'+n
}
let money =flowRight(currency,fixed,double)
console.log(money(10000));
// $20000.00
解决异步并发问题
哨兵变量
通过在全局设置一个变量,每次异步执行成功进行变量修改,直到满足预定条件,进行输出
缺点: 无法对某一具体异步进行操作。
函数式编程
比哨兵变量的方法稍做优化,通过函数对变量进行缓存,无需在全局进行设置,
缺点: 整个过程我们无法监控,因为在使用函数的时候 我们没有处理的过程
发布订阅
const fs = require("fs");
const path = require("path");
// 发布订阅的核心就是将订阅函数存放到数组中,稍后事情发生 了 循环数组依次调用
// 不订阅也能发布 (订阅和发布之间没有任何关系)
let school = {};
let events = {
_arr: [],
on(callback) {
// 将要订阅的函数保存起来
this._arr.push(callback);
},
emit(key, value) {
school[key] = value;
this._arr.forEach((callback) =>
callback(school));
}
};
events.on((data) => {
if (Object.keys(data).length === 2) {
console.log(data);
} });
events.on((data) => { console.log("读取一个完毕", data);
});
fs.readFile(path.resolve(__dirname, "age.txt"),
"utf8", function (err, data) {
events.emit("age", data);
});
fs.readFile(path.resolve(__dirname, "name.txt"),
"utf8", function (err, data) {
events.emit("name", data);
});
// 发布订阅模式,可以监控到每次完成的情况,而且可以自己控 制逻辑