compose 方法
compose 函式定義
compose方法利用陣列的 reduce 方法,將多個方法按照一定的順序組合為一個方法,從而達到一種函式自動有序執行的效果。其中各個方法的執行的順序取決於 reduce 方法如何應用。compose的簡單實現如下:
// compose 接受多個函式作為入參 fun1 fun2
function compose(...funs){
const len = funs.length
// 處理兩種特殊情況,函式數量 為 0 || 1
if(!len) return (...arg) => arg
if(len === 1) return funs[0]
// 以下組合方式 b 方法 會 先於 a 方法執行
return funs.reduce((a,b) => (...arg) => a(b(arg)))
// 以下組合方式 a 方法會先於 b 方法執行
// return funs.reduce((a,b) => (...arg) => b(a(arg)))
}
被組合函式(a,b)的形式
b 函式的返回值為普通的資料,作為a 函式的入參
function b(){ // 如返回值為普通的物件 return {...} } function a(arg){ // arg 資料應用 || 處理 return {...} }
按照以上形式的這種形式,a 函式若想應用b函式的返回值,最好預先知道b函式返回值的資料型別及欄位細節,這會導致 a,b 方法之間的耦合嚴重。
b 返回一個函式,作為 a 函式的入參
function b(){ // 返回值為函式 return (...arg) => { //... } } function a(next){ // next 前置操作 // next() 可以在這裡操作 next // next 後置操作 return (...arg) => { // next 前置操作 // next() 可以在這裡操作 next // next 後置操作 } }
相比上一種形式,這種形式可以使得函式之間達到解偶效果,即函式 a 無需關心函式 b 的返回值,只要知道其返回值是一個函式,並在自己認為合適的時機呼叫即可,這樣同時也對函式呼叫順序控制,從而達到類似洋蔥模型的執行效果。
applyMiddleware 方法
applyMiddleware函式形式
構造閉包,為外部定義的方法,提供內部指定的apis;如下示例
// 接受外部定義的方法陣列 funs function applyMiddleware(...funs){ const apis = { dispatch: () => ({}) } // 遍歷執行 外部定義的方法,構造一個閉包;並將返回值作為 結果返回 return funs.map(fun => fun(apis)) }
被操作的funs函式形式
為了構造一個閉包,fun 的返回值應該是一個使用了 apis 的函式。
function myMiddleware1(apis){ // 返回一個 方法,該方法中會呼叫 apis 中暴露的方法,形成一個閉包, return actions => { // apis 方法呼叫 const { dispatch } = apis dispatch(actions) } } const myMiddlewareWrappers = applyMiddleware(myMiddleware1) // myMiddlewareWrappers 為 [actions => { const { dispatch } = apis; dispatch(actions);}]
可能會有疑問:既然只是 apis 內的函式呼叫,為什麼要這麼複雜,藉助閉包實現?呼叫者直接通過呼叫 apis 內部的函式不就能達到目的?
閉包的應用:
- 閉包可以使我們在函式外部控制函式內部的執行邏輯,如常見的防抖、截流函式
- 利用閉包可以實現函式柯里化,從而達到函式入參快取的效果,進而達到一種單例的效果
為什麼不直接呼叫 apis 內部的方法:
- apis 內部的方法並不是一開始就存在的,可能是動態生成的,通過閉包可以起到1.b 所提到的單例效果
- 通常我們並不是單純的函式呼叫,往往附帶著額外的操作,不應用閉包會導致以下效果
function fun1(arg){ console.log(arg) apis.dispatch(arg) } function fun2(arg){ arg.b = 100 apis.dispatch(arg) } // apis 方法的呼叫可能會隨處可見,但是仔細觀察,apis 方法的呼叫其實都是重複的程式碼 對比應用閉包的效果 // 沒有重複的 apis 內部函式呼叫,構造的閉包函式會自動執行 apis 內部的方法 // apis 暴露了但是沒有完全暴露 const results = applyMiddleware(myMiddleware1,myMiddleware1) results[0](100) results[1](200)
結合
如何結合 compose 與 applyMiddleware 兩個方法,得到魔法效果呢?
首先根據以上定義的 myMiddleware 形式的方法進行考慮,直接將 applyMiddleware 得到的 方法列表進行 composeconst myMiddlewareWrappers = applyMiddleware(myMiddleware1, myMiddleware2) // 通過 compose 將 方法列表組合為一個方法 const result = compose(...myMiddlewareWrappers) // 此時 result 為何種形式呢? // 根據 compose 的 作用,不難想象 result 為一個函式 // actions => myMiddleware1(myMiddleware2(actions))
到這裡,已經將 compose 與 applyMiddleware 結合起來了。
但是仔細觀察 myMiddleware1、2 函式的形式,會發現一些問題,myMiddleware1、2方法返回值不是一個函式,經過開篇對compose的分析可以得到,這種形式會帶來函式之間 耦合嚴重 的問題。因此需要對myMiddleware進行一步改造,如下function myMiddleware3(apis){ // 返回一個 方法,該方法中會呼叫 apis 中暴露的方法,形成一個閉包, // 同時為了 保證 compose 過程中 每個 函式的 入參仍然為 一個函式,需要將以下返回值再 // 進行一層封裝 return next => actions => { // ... next(actions) // ... } }
魔法
但是我們還是不滿足以上的結果,apis 內部函式的呼叫不在函式呼叫者手裡,即,函式呼叫者只能傳入一個 actions 引數,而無法控制apis 函式的具體執行時機。如何達到控制反轉的效果?
actions 其實可以是一個方法,此時就可以通過傳參的方式將 apis 再傳遞給 actions,從而將 apis 控制權交給真正的函式呼叫者。如下function myMiddleware2(apis){ // 返回一個 方法,該方法中會呼叫 apis 中暴露的方法,形成一個閉包, return next => actions => { // 控制反轉 actions(apis) } } // 可以 結合myMiddleware2 與myMiddleware1 兩種形式的函式實現相容 // 即 呼叫者需要 控制 apis 時通過 傳入函式形式的 actions 即可,否則傳入 apis 中指定形式入參即可 function myMiddleware2(apis){ // 返回一個 方法,該方法中會呼叫 apis 中暴露的方法,形成一個閉包, return next => actions => { // 控制反轉 if(typeof actions === 'function') return actions(apis); next(actions) } }