函数式编程
- 为什么要学习函数式编程
- 函数式编程的特性(纯函数,柯里化,函数组合)
- 函数式编程的应用场景
- 函数式编程库Lodash
为什么要学习函数式编程
- 函数式编程随着React的流行受到越来越多的关注,例如高阶组件、hook
- Vue3也开始拥抱函数式编程
- 函数式编程可以抛弃this
- 打包的过程中可以更好地利用tree shaking过滤无用代码
- 方便测试,方便并行处理
- 有较多类库帮助我们进行函数式开发:lodash、underscore、ramda
函数式编程概念
函数式编程(Function Programming,FP),函数式编程是编程范式之一。常用的范式面向过程编程、面向对象编程
面向对象编程思维方式
将事物抽象成类与对象,通过封装继承多态来演示事物事件的联系,例如耳熟能详的鸭子模式。
函数式编程思维方式
把事物与事件抽象成函数(对运算过程进行抽象)
- 通过某种运算符进行输入输出
- x->f(映射,联系)->y,y=f(x)
- 函数式编程中的函数并不是程序中的函数,而是指数学中的函数及映射关系。例如 y = sinx(x) 表述的就是 x 与 y 的关系
- 相同的输入总是要得到相同的结果(纯函数)
- 函数式编程用来描述数据(函数)之间的映射
- 抽离的函数可以让代码更好的复用,且可用通过函数组合成为更强大的函数
简单的函数式编程与面向过程编程对比
计算两个数多的和
// 面向过程编程方式
let num1 = 1
let num2 = 2
let num = num1 + num2
console.log(num) // 3
// 函数式编程
function add( n1, n2 ){
return n1 + n2
}
let sum = add( 1, 2)
console.log(sum)
前置知识
函数式一等公民
- 函数可以存储在变量中
- 函数作为参数
- 函数作为返回值
- 在JavaScript 函数就是一个对象
函数可以存储在变量中
// 函数可以存储在变量中
let fn = add( n1, n2 ){
return n1 + n2
}
fn(1,2)//3
函数作为返回值
// 一个类中有常用的CURD方法,如下实现
const BlogController = {
index(props) {
return Views.index(posts)
},
show(props) {
return Views.show(posts)
},
create(props) {
return Views.create(posts)
},
update(props) {
return Views.update(posts)
},
destory(props) {
return Views.destory(posts)
},
}
#### 简化代码,调用转为引用赋值
const BlogController = {
index: Views.index,
show: Views.show,
create: Views.create,
update: Views.update,
destory: Views.destory,
}
函数作为参数 (高阶函数)
- 高阶函数(Height-oeder funciton)
- 把函数作为参数传递给另一个函数
- 可以把函数作为另外一个函数的返回结果
- 函数作为参数
- 函数作为参数会使函数更加灵活
- 不需要考虑内部实现方法
- 函数的名字具有实际意义
模拟forEach
//定义函数
function forEach(array,fn){
for(let i = 0 ; i< array.length; i++){
fn(i,array[i])
}
}
let arr = [ 1, 2, 3, 4, 5]
// 调用函数
forEach(arr,function(value,index){
console.log(index,value)
})
模拟filter
function filter (array, fn){
let results = []
for(let i = 0 ; i< array.length; i++){
// 使用短路运算符
fn(array[i])&&results.push(array[i])
}
return results
}
const arr = [ 1, 2, 3, 4, 5]
const filterArr = filter(arr,function(value){
// 过滤出所有的偶数
return value%2===0
})
console.log(filterArr) // [ 2, 4 ]
函数作为返回值
简单示例语法
function makFn(){
const msg = 'hello'
return function(){
console.log(msg)
}
}
const fn = makFn()
fn()
换一种方式调用
function makFn(){
const msg = 'hello'
return function(){
console.log(msg)
}
}
makFn()()
封装简单的once函数
function once (fn) {
let done = false
return function(){
if(!done){
done = true
return fn.apply(this,arguments)
}
}
}
// 实际业务中 发起支付行为
let pay = once(function(money){
console.log(`支付金额为${money}`)
})
pay(5) // 支付金额为5
pay(10) // 未执行
使用高阶函数的意义
- 抽象可以帮助我们屏蔽细节,只需要关注我们的目标
- 高阶函数用来解决抽象通用性的问题
- 使代码更简洁,函数的名字具有实际意义
常用的高阶函数
- forEach
- map
- filter
- every
- some
- find/findIndex
- reduce
- sort
- …
模拟map
const map = (array, fn)=>{
let results = []
for(let val of array){
results.push(fn(val))
}
return results
}
const arr = [ 1, 2, 3]
const results = map(arr,(v)=>v*v) // 这里使用了ES6的箭头函数,不理解的道友可以翻翻之前的文章
console.log(results) // [ 1, 4, 9 ]
模拟every
const every = (array, fn)=>{
for(let val of array){
if(!fn(val)){
return false
}
}
return true
}
const arr = [ 1, 2, 3]
const result = every(arr,(v)=>v>2)
console.log(result) // false
模拟some
const some = (array, fn)=>{
for(let val of array){
if(fn(val)){
return true
}
}
return false
}
const arr = [ 1, 2, 3]
const result = some(arr,(v)=>v>2)
console.log(result) // true
闭包(万年面试题)
- 函数和其周围的状态(词法环境)的引用捆绑在一起形成的闭包
- 可以再另一个作用域中调用一个函数内部函数并访问到该函数的作用域中成员
下面这段代码就是一个简单的闭包案例
function makFn(){
const msg = 'hello'
return function(){
console.log(msg)
}
}
const fn = makFn()
fn()
function once (fn) {
let done = false
return function(){
if(!done){
done = true
return fn.apply(this,arguments)
}
}
}
函数执行时会放到一个执行栈上当函数执行完毕之后会从执行站上移除,但是堆上的作用域成员因为被外部引用不能释放,因此内部函数依然可以访问外部函数的成员
闭包案例
封装生成任意次方函数
console.log(Math.pow(1,2)) // 1
console.log(Math.pow(2,2)) // 4
console.log(Math.pow(4,2)) // 16
function makePower(power){
return function(num){
return Math.pow(num,power)
}
}
const powerTwo = makePower(2) //生成一个平方方法
console.log(powerTwo(1)) // 1
console.log(powerTwo(2)) // 4
console.log(powerTwo(4)) // 16
求员工工资
小明同学下班前接到财务需求计算员工工资,业务逻辑如下
- 员工工资分为基本工资与绩效工资
- 级别相同基本工资相等 分别有 12000 与 15000 两个级别
- 绩效根据考勤、绩效等计算所以绩效工资没人略有差异
小明不假思索
赶紧下班的写下如下代码
粗俗的面向过程
const xiaoLi = 12000 + 2000
const xiaoHong = 12000 + 1000 // 可能是迟到的次数太多
const liLei = 15000 + 15000 // 超额完成任务,绩效大佬
const hangMeiMei = 15000 + 5000 // 本本分分的老实人
小明6点准时提交代码,下班走人。不赶紧跑路难道等着需求变更
回到家的小明无所事事
没有女朋友。日常review时想到不对我学过函数式编程,FP大法好。我要用函数
常规函数式编程
function sumSalary(base,perFormance){
return base + perFormance
}
const xiaoLi = sumSalary(12000,2000)
const xiaoHong = sumSalary(12000,1000)
const liLei = sumSalary(15000,15000)
const hangMeiMei = sumSalary(15000,5000)
满心欢喜的看着自己的代码,add push 一顿操作钻进自己的~~冷~被窝
早会上,老大发言:小明还是比较爱学习的么,虽然每天6点准时走人。但是回见还是工作的,
MMP能不能干点正事,我都没走你就先走。但是年轻人还是要多动脑子的,来看看我修改的管理准则之装X大法。虽然小明用了函数式编程但是,如果还有级别3456789呢,公司上市后员工过万呢。陷入YY无法自拔
函数式编程与闭包
// 生成级别对应基本工资的函数
function makSalary(base){
return function(perFormance){
return base + perFormance
}
}
// 定义员工级别与薪资计算
const salaryLevel1 = makSalary(12000)
const salaryLevel2 = makSalary(15000)
// 计算员工工资
const xiaoLi = salaryLevel1(2000)
const xiaoHong = salaryLevel1(1000)
const liLei = salaryLevel2(15000)
const hangMeiMei = salaryLevel2(5000)
// ... 还有更多小红 小黄 小蓝什么的
多思考、善总结、熟悉业务且理解它,这样才能以不变应万变
不加班。