為什么學(xué)習(xí)函數(shù)式編程
- 函數(shù)式編程隨著react的流行受到越來越多的關(guān)注
- vue3也開始擁抱函數(shù)式編程
- 函數(shù)式編程可以拋棄this
- 打包過程中可以更好的利用tree shaking過濾無用代碼
- 方便測試籍嘹,方便并行處理
很多庫可以幫助我們進行函數(shù)式開發(fā):lodash,underscore,remda
函數(shù)相關(guān)復(fù)習(xí)
函數(shù)是一等公民
把函數(shù)賦值給變量
//把函數(shù)賦值給變量
let fn=function(){
console.log("hello")
}
fn()
//一個事例
const blogController={
index(props){return views.index(props)},
show(props){return views.show(props)},
create(props){return views.create(props)},
update(props){return views.update(props)},
destroy(props){return views.destroy(props)},
}
//優(yōu)化
const blogController={
index:views.index,
show:views.show,
create:views.create,
update:views.update,
destroy :views.destroy
}
高階函數(shù)
高階函數(shù)-函數(shù)作為參數(shù)
function forEach(array ,fn){
for(let i=0;i<array.length;i++){
fn(array[i])
}
}
let arr=[1,2,3,5]
forEach(arr,(item)=>{
console.log(item)
})
function filter(array,fn){
let results=[]
for(let i=0;i<array.length;i++){
if(fn(arrat[i])){
results.push(array[i])
}
}
}
let arr=[1,2,3,5]
let res = filter(arr,()=>{
return item % 2 === 0
})
高階函數(shù)-函數(shù)作為返回值
function makeFn(){
let msg='hello'
return function(){
console.log(msg)
}
}
makeFn()() //"hello"
function once(fn){
let done = false
return function(){
if(!done){
done=true
fn.apply(this,arguments)
}
}
}
let pay=once(function (money){
console.log(`支付:${money}rmb`)
})
//模擬常用高階函數(shù)map黑忱,every,some
const map=(array,fn)=>{
let results=[]
for(let value of array){
results.push(fn(value))
}
return results
}
const every=(array,fn)=>{
let result=true
for(let value of array){
result=fn(value)
if(!result) break
}
return result
}
const some=(array,fn)=>{
let result=false
for(let value of array){
result=fn(value)
if(result) break
}
return result
}
let arr=[1,5,6,8]
arr=map(arr,v=>v*v)
let res=every(arr,v=>v>10)
let r=some(arr,v=>v % 2 === 0)
閉包
可以在另一個作用域中調(diào)用一個函數(shù)內(nèi)部函數(shù)并訪問到該函數(shù)的作用域中的成員
function makeFn(){
let mes="hello"
return function(){
console.log(msg)
}
}
const fn=makeFn()
fn()
閉包的本質(zhì):函數(shù)在執(zhí)行的時候會放到執(zhí)行棧上當(dāng)函數(shù)執(zhí)行完畢后會從執(zhí)行棧上移除匾灶,但堆上的作用域成員因為被外部引用不能釋放瞬逊,因此內(nèi)部函數(shù)依然可以訪問外部函數(shù)的成員。
函數(shù)式編程基礎(chǔ)
純函數(shù)
純函數(shù)必須有參數(shù)和返回值蒙挑,并且相同的輸入始終會得到相同的輸出,而且沒有任何可觀察的副作用
let arr=[1,2,3,4,5]
//多次調(diào)用
arr.slice(0,3)//輸出相同愚臀,純函數(shù)
arr.splice(0,3)//輸出不同忆蚀,不純
- 函數(shù)式編程不會保留計算中間的結(jié)果,所以變量是不可變的
- 我們可以吧一個函數(shù)的執(zhí)行結(jié)果交給另一個函數(shù)去處理
lodash純函數(shù)的代表
const _=require('lodash')
const array=['jack','bob','tim','lucy']
純函數(shù)的好處
- 可緩存姑裂,提高函數(shù)性能
const _=require('lodash')
function getArea(r){
return MathPI*r*r
}
//模擬memoize
function memoize(fn)
let cache={}
return function(){
let key = JOSN.stringify(arguments)
cache[key]=cache[key]||f.apply(f,arguments)
return cache[key]
}
}
可測試
并行處理
1.多線程環(huán)境下操作共享內(nèi)存數(shù)據(jù)很可能出現(xiàn)意外情況
2.純函數(shù)不需要訪問共享的內(nèi)存數(shù)據(jù)馋袜,所以在并行環(huán)境可一任意運行純函數(shù)
柯里化
function checkAge(min){
return function (age){
return age>=min
}
}
let checkAge=min=>(age=>age>=min)
let check18=checkAge(18)
let check20=checkAge(20)
console.log(check18(24))
function curry(fun){
return function curriedFn(...args){
if(args.length<func.length){
return function(){
return curriedFn(...args.concant(Array.from(arguments)))
}
}
}
}
- 當(dāng)一個函數(shù)有多個參數(shù)的時候先傳遞一部分參數(shù)調(diào)用他
- 然后返回一個新的函數(shù)接收剩余的參數(shù),返回結(jié)果
柯里化總結(jié)
- 柯里化可以讓我們給一個函數(shù)傳遞較少的參數(shù)得到一個已經(jīng)記住某些固定參數(shù)的新函數(shù)
- 這是一種對函數(shù)參數(shù)的緩存
- 讓函數(shù)變得更加靈活舶斧,讓函數(shù)的粒度更小
- 可以吧多元函數(shù)轉(zhuǎn)化成一元函數(shù)欣鳖,可以組合使用函數(shù)產(chǎn)生強大的功能
函數(shù)組合
- 概念:如果一個函數(shù)要經(jīng)過多個函數(shù)處理才能得到最終的結(jié)果,這時候可以吧中間過程的函數(shù)合并成一個函數(shù)
- 函數(shù)組合默認(rèn)是從右到左執(zhí)行
function compose(){}
lodash中FP模塊-函數(shù)式編程柯里化后的模塊
如果有多個參數(shù)時茴厉,函數(shù)優(yōu)先數(shù)據(jù)之后原則
函子
函子Functor
容器:包含值和值的變形關(guān)系
函子:是個特殊容器泽台,通過一個普通對象來實現(xiàn),該對象具有map方法矾缓,map方法可以運行一個函數(shù)對值進行處理
class Container{
static of(value){
return new Container(value)
}
constructor(value){
this._value=value
}
map(fn){
return Container.of(fn(this._value)
}
}
let r=Container.of(5).map(x=>x+1).map(x=>x*x)
console.log(r)//Container { _value: 36 }
總結(jié)
- 函數(shù)是編程運算不直接操作值师痕,而是由函子完成
- 函子就是實現(xiàn)了map契約的對象
- 我們可以把函子想象成盒子,盒子里面封裝了一個值
- 想要處理盒子重的值而账,我們需要給盒子的map方法專遞一個處理值的函數(shù)(純函數(shù))胰坟,由這個函數(shù)對值進行處理
- 最終map方法返回一個包含新值的盒子(函子)
MayBe函子
class MayBe{
static of(value){
return new MayBe(value)
}
constructor(value){
this._value=value
}
map(fn){
return this.isNothing() ? MayBe.of(null) : MayBe.of(fn(this._value)
}
isNothing(){
return this._value === null || this._value === undifined
}
}
Either函子
- Either兩者中的任何一個,類似于if...else...的處理
- 異常會讓函數(shù)變得不純泞辐,Either函子可以用來做異常處理
class Left{
static of(value){
return new Left(value)
}
constructor(value){
this._value=value
}
map(fn){
return this
}
}
class Right{
static of(value){
return new Right(value)
}
constructor(value){
this._value=value
}
map(fn){
return Right.of(fn(this._value)
}
}
function parseJSON(str){
try{
return Right.of(JSON.parse(str))
}catch(e){
return Left.of({error:e.message})
}
}
IO函子
- IO函子中的_value是一個函數(shù)笔横,這里是把函數(shù)作為值來處理
- IO函子可以把不純的動作存儲到_value中,延遲執(zhí)行這個不純的操作
- 把不純的操作交給調(diào)用者來處理
const fp= require('lodash/fp')
class IO{
static of(value){
return new IO(function(){
return value
})
}
constructor(fn){
this._value=fn
}
map(fn){
return new IO(fp.flowRight(fn.this._value))
}
}
let r =IO.of(process).map(p=>p.execPath)
console.log(r._value())
Task函子
const fs =require('fs')
const {task} =require('folktale/cocurrency.task')
const {spilt,find}=require('lodash/fp')
function readFile(filename){
fs.readFile(filename,"utf-8",(err,data)=>{
if(err) resolver.reject(err)
resolver.resolve(data)
})
}
readFile('pakage.json')
.map(split(''/n'))
.map(find(x=>x.inclues('version')))
.run()
.listen({
onRejected:err=>{},
onResolved:value=>{}
})