函數式編程——編程思想、高階函數、部分es6方法模擬、閉包解析及案例

函數式編程

  • 爲什麼要學習函數式編程
  • 函數式編程的特性(純函數,柯里化,函數組合)
  • 函數式編程的應用場景
  • 函數式編程庫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)
// ... 還有更多小紅 小黃 小藍什麼的

多思考、善總結、熟悉業務且理解它,這樣才能以不變應萬變不加班

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章