摘要:場景現有函數,要求在不改寫函數的基礎上,在執行該函數之前添加檢查,檢查返回,再執行我們大都會這樣寫很明顯,這樣的很不靈活如果現在又有,同樣需要在執行之前進行檢查,再寫一個嗎不,修改函數滑水的日子木有幾天,又出現了新的需求,在之前,還有一步操
場景
function stepOne(msg) { console.log(msg) } function checkStepOne(msg) { console.log(`check:${msg}`) return msg === "success" ? true : false }
現有函數 stepOne(),要求在不改寫函數的基礎上,在執行該函數之前添加檢查 checkStepOne(),
檢查返回 ture,再執行 stepOne()
我們大都會這樣寫
function flow(msg){ if(checkStepOne(msg)){ return stepOne(msg) } return false }
很明顯,這樣的 flow() 很不靈活
如果現在又有 stepTwo(),同樣需要在執行之前進行檢查 checkStepTwo(),再寫一個flowTwo() 嗎?
不,修改函數 flow()
function flow(fn, checkFn, msg) { if (checkFn(msg)) { return fn(msg) } return false } flow(stepOne, checkStepOne, "success") flow(stepTwo, checkStepTwo, "success")
滑水的日子木有幾天,又出現了新的需求,在 checkStepOne() 之前,還有一步操作,beforeCheckStepOne()
function beforeCheckStepOne(msg) { console.log(`beforeCheckStepOne is "${msg}"`) }
修改函數 flow()
function flow(fns, msg) { let current = fns.shift() let result while (current) { result = current(msg) if (result === false) { return false } current = fns.shift() } return result } flow([beforeCheckStepOne, checkStepOne, stepOne], "fail") // beforeCheckStepOne is "fail" // checkMsg is "fail"
flow(fns, msg) 中 fns 用來存儲要執行的步驟,如果上一個步驟返回 false,就不繼續下面的步驟了
套路呢?不妨多一些AOP,Aspect-oriented programming,面向切面編程
改寫Function的原型
Function.prototype.before = function (fn) { let rawFn = this return function () { if (fn.apply(null, arguments) === false) { return false } rawFn.apply(null, arguments) } } stepOne.before(checkStepOne).before(beforeCheckStepOne)("success") // beforeCheckStepOne is "success" // checkMsg is "success" // success
再換個花樣
Function.prototype.after = function (fn) { let rawFn = this return function () { if (rawFn.apply(null, arguments) === false) { return false } fn.apply(null, arguments) } } beforeCheckStepOne.after(checkStepOne).after(stepOne)("success") // beforeCheckStepOne is "success" // checkMsg is "success" // success
OS:這樣寫不會被人打嗎?不僅改寫了 Function.prototype,看起來還太裝逼
滑水的日子木有幾天,又出現了新的需求,步驟之間能傳遞額外的消息
改造完,如下,多個 context 對象,用于傳遞信息
function stepOne(msg, context) { console.log(msg) console.log(context.data) } function checkStepOne(msg, context) { console.log(`checkMsg is "${msg}"`) return msg === "success" ? true : false } function beforeCheckStepOne(msg, context) { console.log(`beforeCheckStepOne is "${msg}"`) context.data = "from beforeCheckStepOne" } function flow(fns, msg) { let currentFn = fns.shift() let result let context = {} while (currentFn) { result = currentFn(msg, context) if (result === false) { return false } currentFn = fns.shift() } return result } flow([beforeCheckStepOne, checkStepOne, stepOne], "success")Middle
盜圖自前端開發中的中間件
function middle1(next) { return () => { console.log("Enter the middle1") next() console.log("Exit the middle1") } } function middle2(next) { return () => { console.log("Enter the middle2") next() console.log("Exit the middle2") } } function middle3(next) { return () => { console.log("Enter the middle3") next() console.log("Exit the middle3") } } function next() { console.log("next") } middle1(middle2(middle3(next)))()
這還是3個中間件,調用起來就如此丑陋了,當有更多的中間件該是如何
重寫個flow()函數好了
function flow(funcs, rawNext) { let next = funcs.pop() next = next(rawNext) let middle while (funcs.length > 0) { middle = funcs.pop() next = middle(next) } return next } flow([middle1, middle2, middle3], next)() // Enter the middle1 // Enter the middle2 // Enter the middle3 // next // Exit the middle3 // Exit the middle2 // Exit the middle1
執行 flow() 的過程,就是在拼湊 middle1(middle2(middle3(next))) 的過程
同時,next() 也可以看成是個中間件
function flow(funcs) { let next = funcs.pop() while (funcs.length > 0) { let middle = funcs.pop() next = middle(next) } return next } flow([middle1, middle2, middle3, next])()
沒有定義過多變量的 while,總是可以用 reduceRight 修飾一下
function flow(funcs) { return funcs.reduceRight((a, b) => b(a)) } flow([middle1, middle2, middle3, next])()瞅瞅 redux中compose.js 是怎么寫的
/** * Composes single-argument functions from right to left. The rightmost * function can take multiple arguments as it provides the signature for * the resulting composite function. * * @param {...Function} funcs The functions to compose. * @returns {Function} A function obtained by composing the argument functions * from right to left. For example, compose(f, g, h) is identical to doing * (...args) => f(g(h(...args))). */ export default function compose(...funcs) { if (funcs.length === 0) { return arg => arg } if (funcs.length === 1) { return funcs[0] } return funcs.reduce((a, b) => (...args) => a(b(...args))) }舉個例子,這個 compose 是怎么玩的
如它注釋中所說,compose(f, g, h) is identical to doing (...args) => f(g(h(...args)))
// 輸入16進制字符串,返回8位2進制字符串 let sixTeenToTen = x => parseInt(x, 16) let tenToTwo = x => (x).toString(2) let addZero = x => ("00000000" + x).slice(-8) let sixTeenToTwo = compose(addZero, tenToTwo, sixTeenToTen) console.log(sixTeenToTwo("0x62")) // 01100010
當然,你也可以這樣寫
let sixTeenToTwo2 = x => ("00000000" + (parseInt(x, 16)).toString(2)).slice(-8) console.log(sixTeenToTwo2("0x62")) // 01100010
開心就好
Compose & middle回到之前的middle1,middle2,middle3 函數那,同時把next改寫成middle4
function middle1(next) { return (a) => { console.log("Enter the middle1") next(a) console.log("Exit the middle1") } } function middle2(next) { return (a) => { console.log("Enter the middle2") next(a) console.log("Exit the middle2") } } function middle3(next) { return (a) => { console.log("Enter the middle3") next(a) console.log("Exit the middle3") } } function middle4(next) { return (a) => { console.log(`middle4:${a}`) } } let middles = compose(middle1, middle2, middle3, middle4)() middles("msg") // Enter the middle1 // Enter the middle2 // Enter the middle3 // middle4:msg // Exit the middle3 // Exit the middle2 // Exit the middle1
值得一提的是,let middles = compose(middle1, middle2, middle3, middle4)() 最后有一組(),調用函數,相當于middle1(middle2(middle3(middle4()))) 給 middle4 傳入空參數
執行 middle4(),返回
(a) => { console.log(`middle4:${a}`) }
這個函數,作為 next 參數,執行 middle3(next),返回
(a) => { console.log("Enter the middle3") console.log(`middle4:${a}`) console.log("Exit the middle3") }
這個函數,作為 next 參數,執行 middle2(next),返回
(a) => { console.log("Enter the middle2") console.log("Enter the middle3") console.log(`middle4:${a}`) console.log("Exit the middle3") console.log("Exit the middle2") }
這個函數,作為 next 參數,執行 middle1(next),返回
(a) => { console.log("Enter the middle1") console.log("Enter the middle2") console.log("Enter the middle3") console.log(`middle4:${a}`) console.log("Exit the middle3") console.log("Exit the middle2") console.log("Exit the middle1") }
所以,最終 middles 就是這樣的
let middles = compose(middle1, middle2, middle3, middle4)() // 相當于 let middles = (a) => { console.log("Enter the middle1") console.log("Enter the middle2") console.log("Enter the middle3") console.log(`middle4:${a}`) console.log("Exit the middle3") console.log("Exit the middle2") console.log("Exit the middle1") }高仿express中的use
class Middle { constructor() { this.funcs = [] } use(fn) { this.funcs.push(fn) return this } work() { this.funcs.reduceRight((fn1, fn2) => { return () => fn2(fn1) }, () => {})() } } function m1(next) { console.log("Enter the middle1") next() console.log("Exit the middle1") } function m2(next) { console.log("Enter the middle2") next() console.log("Exit the middle2") } function m3(next) { console.log("Enter the middle3") next() console.log("Exit the middle3") } function m4(next) { console.log("Enter the middle4") console.log("Exit the middle4") } let m = new Middle() m.use(m1) m.use(m2) m.use(m3) m.use(m4) m.work()
來段小插曲
let fns = [m1, m2, m3, m4, m5] fns.reduceRight((fn1, fn2) => () => fn2(fn1), () => {})() // 相當于 fns.reduceRight((fn1, fn2) => { return () => fn2(fn1) }, () => {})() // 結合之前定義的 m1, m2, m3, m4, m5, 得到結果 // Enter the middle1 // Enter the middle2 // Enter the middle3 // Enter the middle4 // Exit the middle4 // Exit the middle3 // Exit the middle2 // Exit the middle1
其實那段 reduceRight,本來是寫成 while 的
let fns = [m1, m2, m3, m4, m5] let next = () => {} while(fns.length > 0){ let fn = fns.pop() next = () => fn(next) } next() // 一直輸出 Enter the middle1
所以做了些調整
let fns = [m1, m2, m3, m4, m5] let next = () => {} while (fns.length > 0) { let fn = fns.pop() next = function (fn, next) { return () => fn(next) }(fn, next) } next() // 輸出結果符合預期
來自網上的套路是這樣的
class Middle { constructor() { this.funcs = [] this.middlewares = [] } use(fn) { this.funcs.push(fn) return this } next(fn) { if (this.middlewares && this.middlewares.length > 0) { let ware = this.middlewares.shift() ware.call(this, this.next.bind(this)) } } work() { this.middlewares = this.funcs.map(f => f) this.next() } }
感覺大概就是這個意思
m4 = m4.bind(null, m5) m3 = m3.bind(null, m4) m2 = m2.bind(null, m3) m1 = m1.bind(null, m2) m1() // 或者 m1.call(null, m2.bind(null, m3.bind(null, m4.bind(null, m5))))
再啰嗦地解釋下,因為我一開始是看半天沒能理解
let m = new Middle() m.use(m1) m.use(m2) m.use(m3) m.use(m4) m.use(m5) m.work()
執行 m.work() 后,
執行 m.next()
從 m.middlewares 中取出 m1
執行 m1.call(m, m.next)
執行 m1 函數體內
console.log("Enter the middle1")
然后遇到 next()
實際上執行了 m.next()
從 m.middlewares 中取出 m2
執行 m2.call(m, m.next)
執行 m2 函數體內
console.log("Enter the middle2")
然后遇到 next()
實際上執行了 m.next()
從 m.middlewares 中取出 m3
執行 m3.call(m, m.next)
執行 m3 函數體內
console.log("Enter the middle3")
...
直至結束
共享數據class Middle { constructor() { this.funcs = [] this.middlewares = [] this.options = null } use(fn) { this.funcs.push(fn) return this } next(fn) { if (this.middlewares && this.middlewares.length > 0) { let ware = this.middlewares.shift() ware.call(this, this.options, this.next.bind(this)) } } work(options) { this.middlewares = this.funcs.map(f => f) this.options = options this.next() } }
使用樣例
function m1(options, next) { console.log("Enter the middle1") console.log(options.name) next() console.log("Exit the middle1") } function m2(options, next) { options.name = "m2" console.log("Enter the middle2") console.log(options.name) next() console.log("Exit the middle2") } function m3(options, next) { options.name = "m3" console.log("Enter the middle3") console.log(options.name) next() console.log("Exit the middle3") } function m4(options, next) { console.log("Enter the middle4") console.log(options.name) console.log("Exit the middle4") } function m5(options, next) { console.log("Enter the middle5") next() console.log("Exit the middle5") } let m = new Middle() m.use(m1) m.use(m2) m.use(m3) m.use(m4) m.use(m5) m.work({ name: "m" }) // Enter the middle1 // m // Enter the middle2 // m2 // Enter the middle3 // m3 // Enter the middle4 // Exit the middle4 // Exit the middle3 // Exit the middle2 // Exit the middle1
同樣功能的代碼
let fns = [m1, m2, m3, m4, m5] let next = () => {} let options = { name: "m" } while (fns.length > 0) { let fn = fns.pop() next = function (fn, options, next) { return () => fn(options, next) }(fn, options, next) } next()
同樣功能的代碼
let options = { name: "m" } m4 = m4.bind(null, options, m5) m3 = m3.bind(null, options, m4) m2 = m2.bind(null, options, m3) m1 = m1.bind(null, options, m2) m1() // 相當于 fns.reduceRight((fn1, fn2) => fn2.bind(null, options, fn1))()
同樣功能的代碼
let options = { name: "m" } m44 = () => m4(options, m5) m33 = () => m3(options, m44) m22 = () => m2(options, m33) m11 = () => m1(options, m22) m11() // 相當于 fns.reduceRight((fn1, fn2) => { return () => fn2(options, fn1) }, () => {})() // 再精煉的話 fns.reduceRight((fn1, fn2) => () => fn2(options, fn1), () => {})() // 感覺我3min以后就不認得自己寫的代碼了
fn.bind(null, args) 和 return () => fn(args) 在一些場合,功能相同
參考資料編寫可維護代碼之“中間件模式”
前端開發中的中間件
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/93652.html
摘要:掘金原文地址譯文出自掘金翻譯計劃譯者請持續關注中文維護鏈接獲取最新內容。由于以下的瀏覽器市場份額已逐年下降,所以對于瀏覽器技巧三視覺效果前端掘金揭秘學習筆記系列,記錄和分享各種實用技巧,共同進步。 沉浸式學 Git - 前端 - 掘金目錄 設置 再談設置 創建項目 檢查狀態 做更改 暫存更改 暫存與提交 提交更改 更改而非文件 歷史 別名 獲得舊版本 給版本打標簽 撤銷本地更改... ...
摘要:前戲補上參會的完整記錄,這個問題從一開始我就是準備自問自答的,希望可以通過這種形式把大會的干貨分享給更多人。 showImg(http://7xqy7v.com1.z0.glb.clouddn.com/colorful/blog/feday2.png); 前戲 2016/3/21 補上參會的完整記錄,這個問題從一開始我就是準備自問自答的,希望可以通過這種形式把大會的干貨分享給更多人。 ...
摘要:面試的公司分別是阿里網易滴滴今日頭條有贊挖財滬江餓了么攜程喜馬拉雅兌吧微醫寺庫寶寶樹海康威視蘑菇街酷家樂百分點和海風教育。 (關注福利,關注本公眾號回復[資料]領取優質前端視頻,包括Vue、React、Node源碼和實戰、面試指導) 本人于7-8月開始準備面試,過五關斬六將,最終抱得網易歸,深深感受到高級前端面試的套路。以下是自己整理的面試題匯總,不敢藏私,統統貢獻出來。 面試的公司分...
摘要:原作者原鏈接基于多入口生成模板用于服務端渲染的方案及實戰法律聲明警告本作品遵循署名非商業性使用禁止演繹未本地化版本協議發布。這是什么背景現代化的前端項目中很多都使用了客戶端渲染的單頁面應用。 原作者:@LinuxerPHL原鏈接:基于 Webpack 4 多入口生成模板用于服務端渲染的方案及實戰 法律聲明 警告:本作品遵循 署名-非商業性使用-禁止演繹3.0 未本地化版本(CC BY-...
摘要:原作者原博文地址基于多入口生成模板用于服務端渲染的方案及實戰法律聲明警告本作品遵循署名非商業性使用禁止演繹未本地化版本協議發布。這是什么背景現代化的前端項目中很多都使用了客戶端渲染的單頁面應用。 原作者:@LinuxerPHL原博文地址: 基于 Webpack 4 多入口生成模板用于服務端渲染的方案及實戰 法律聲明 警告:本作品遵循 署名-非商業性使用-禁止演繹3.0 未本地化版本(...
閱讀 2950·2023-04-26 01:49
閱讀 2079·2021-10-13 09:39
閱讀 2291·2021-10-11 11:09
閱讀 931·2019-08-30 15:53
閱讀 2823·2019-08-30 15:44
閱讀 930·2019-08-30 11:12
閱讀 2986·2019-08-29 17:17
閱讀 2379·2019-08-29 16:57