1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        【W(wǎng)eb技術(shù)】988- 深入理解洋蔥模型

        共 7741字,需瀏覽 16分鐘

         ·

        2021-06-19 14:04

        作者:掘金@蘇里  

        https://juejin.im/post/6844904025767280648

        前言

        本文來由,希望可以剖析中間件的組合原理,從而幫助大家更加理解洋蔥模型。

        話不多說,正文如下。

        這一段代碼來源于 redux 里導(dǎo)出的 compose 函數(shù)。我做了一些修改。主要是給匿名函數(shù)添加了名稱,比如 reducer 和 nextWrapper,主要原因是匿名函數(shù)(anonymous)不便于調(diào)試。所以 《You-Dont-Know-JS》 的作者 Kyle Simpson 大叔就對(duì)箭頭函數(shù)持保留意見,認(rèn)為不該亂用,不過跑題了,扯回。

        先貼代碼如下。

        function compose(...funcs) {
          if (funcs.length === 0) {
            return arg => arg;
          }

          if (funcs.length === 1) {
            return funcs[0];
          }

          return funcs.reduce(function reducer(a, b) {
            return function nextWrapper(...args) {
              return a(b(...args));
            };
          });
        }

        接下來全文將基于此函數(shù)剖析。

        接下來將提供幾個(gè)簡單 redux 中間件,同樣,我避免了箭頭函數(shù)的使用,理由同上。代碼如下:

        function next(action) {
          console.log("[next]", action);
        }

        function fooMiddleware(next) {
          console.log("[fooMiddleware] trigger");
          return function next_from_foo(action) {
            console.log("[fooMiddleware] before next");
            next(action);
            console.log("[fooMiddleware] after next");
          };
        }

        function barMiddleware(next) {
          console.log("[barMiddleware] trigger");
          return function next_from_bar(action) {
            console.log("[barMiddleware] before next");
            next(action);
            console.log("[barMiddleware] after next");
          };
        }

        function bazMiddleware(next) {
          console.log("[bazMiddleware] trigger");
          return function next_from_baz(action) {
            console.log("[bazMiddleware] before next");
            next(action);
            console.log("[bazMiddleware] after next");
          };
        }

        此時(shí)如果將以上 foo bar baz 三個(gè)中間件組合運(yùn)行如下:

        const chain = compose(fooMiddleware, barMiddleware, bazMiddleware);
        const nextChain = chain(next);
        nextChain("{data}");

        以上將會(huì)在控制臺(tái)輸出什么?

        大家可以思考一下。

        ...

        熟悉中間件運(yùn)行順序的同學(xué)可能很快得出答案:

        [bazMiddleware] trigger
        [barMiddleware] trigger
        [fooMiddleware] trigger
        [fooMiddleware] before next
        [barMiddleware] before next
        [bazMiddleware] before next
        [next] {data}
        [bazMiddleware] after next
        [barMiddleware] after next
        [fooMiddleware] after next

        寫不出正確答案的同學(xué)也無須灰心。這篇文章的目的,正是幫助大家更好理解這一套機(jī)制原理。

        這種洋蔥模型,也即是中間件的能力之強(qiáng)大眾所周知,現(xiàn)在在 Web 社區(qū)發(fā)揮極大作用的 Redux、Express、Koa,開發(fā)者利用其中的洋蔥模型,構(gòu)建無數(shù)強(qiáng)大又有趣的 Web 應(yīng)用和 Node 應(yīng)用。更不用提基于這三個(gè)衍生出來的 Dva、Egg 等。所以其實(shí)需要理解的是這套實(shí)現(xiàn)機(jī)制原理,如果光是記住中間件執(zhí)行順序,未免太過無趣了,現(xiàn)在讓我們逐層逐層解構(gòu)以上代碼來探索洋蔥模型吧。

        到這里,正文正式開始!

        以上代碼的靈魂之處在于 Array.prototype.reduce(),不了解此函數(shù)的同學(xué)強(qiáng)烈建議去 MDN 遛跶一圈 MDN | Array.prototype.reduce()。

        reduce 函數(shù)是函數(shù)式編程的一個(gè)重要概念,可用于實(shí)現(xiàn)函數(shù)組合(compose)

        組合中間件機(jī)制

        const chain = compose(fooMiddleware, barMiddleware, bazMiddleware);
          

        以上 compose 傳入了 fooMiddleware、barMiddleware、bazMiddleware 三個(gè)中間件進(jìn)行組合,內(nèi)部執(zhí)行步驟可以分解為以下兩步。

        1. 第一步輸入?yún)?shù):a -> fooMiddleware,b -> barMiddleware

        執(zhí)行 reduce 第一次組合,得到返回輸出:function nextWrapper#1(...args) { return fooMiddleware(barMiddleware(...args)) }

        1. 第二步輸入?yún)?shù):a -> function nextWrapper#1(...args) { return fooMiddleware(barMiddleware(...args)) },b -> bazMiddleware

        執(zhí)行 reduce 第二次組合,得到返回輸出:function nextWrapper#2(...args) { return nextWrapper#1(bazMiddleware(...args)) }

        所以 chain 就等于最終返回出來的 nextWrapper。

        (這里用了 #1,#2 用來指代不同組合的 nextWrapper,實(shí)際上并沒有這樣的語法,須知)

        img

        應(yīng)用中間件機(jī)制

        然而此時(shí)請(qǐng)留意,所有中間件并沒有執(zhí)行,到目前為止最終通過高階函數(shù) nextWrapper 返回了出來而已。

        因?yàn)橹钡揭韵逻@一句,傳入 next 函數(shù)作為參數(shù),才開始真正的觸發(fā)了 nextWrapper,開始迭代執(zhí)行所有組合過的中間件。

        const nextChain = chain(next);
          

        我們?cè)谏厦娴弥?chain 最終是形如(...args) => fooMiddleware(barMiddleware(bazMiddleware(...args)))的函數(shù)。因此當(dāng)傳入 next 函數(shù)時(shí),內(nèi)部執(zhí)行步驟可以分為下述幾步:

        1. 第一步,執(zhí)行 chain 函數(shù)(也即是 nextWrapper#2),從 compose 的函數(shù)組合從內(nèi)至外,next 參數(shù)首先交由 bazMiddleware 函數(shù)執(zhí)行,打印日志后,返回了函數(shù) next_from_baz。
        2. 第二步,next_from_baz 立即傳入 nextWrapper#1,返回了 fooMiddleware(barMiddleware(...args))。因此,barMiddleware 函數(shù)接收的期望 next 參數(shù),其實(shí)并不是我們一開始的 next 函數(shù)了,而是 bazMiddleware 函數(shù)執(zhí)行后返回的 next_from_baz。barMiddleware 收到 next 參數(shù)開始執(zhí)行,打印日志后,返回了 next_from_bar 函數(shù)。
        3. 第三步,同理,fooMiddleware 函數(shù)接收的期望 next 參數(shù)是 barMiddleware 函數(shù)執(zhí)行后返回的 next_from_bar。fooMiddleware 收到 next 參數(shù)開始執(zhí)行,打印日志并返回了 next_from_foo 函數(shù)。

        所以此時(shí)我們此時(shí)可知,運(yùn)行完 chain 函數(shù)后,實(shí)際上 nextChain 函數(shù)就是 next_from_foo 函數(shù)。

        再用示意圖詳細(xì)描述即為:

        img

        此時(shí)經(jīng)過以上步驟,控制臺(tái)輸出了下述日志:

        [bazMiddleware] trigger
        [barMiddleware] trigger
        [fooMiddleware] trigger
          

        這里的 next_from_baz,next_from_bar,next_from_foo 其實(shí)就是一層層的對(duì)傳入的參數(shù)函數(shù) next 包裹。官方說法稱之為 Monkeypatching。

        我們很清晰的知道,next_from_foo 包裹了 next_from_bar,next_from_bar 又包裹了 next_from_baz,next_from_baz 則包裹了 next。

        如果直接寫 Monkeypatching 如下

        const prevNext = next;
        next = (...args) => {
          // @todo
          prevNext(...args);
          // @todo
        };
          

        但這樣如果需要 patch 很多功能,我們需要將上述代碼重復(fù)許多遍。的確不是很 DRY。

        Monkeypatching 本質(zhì)上是一種 hack?!皩⑷我獾姆椒ㄌ鎿Q成你想要的”。

        關(guān)于 Monkeypatching 和 redux 中間件的介紹,十分推薦閱讀官網(wǎng)文檔 Redux Docs | Middleware。

        到這里我想出個(gè)考題,如下:

        function add5(x) {
          return x + 5;
        }

        function div2(x) {
          return x / 2;
        }

        function sub3(x) {
          return x - 3;
        }

        const chain = [add5, div2, sub3].reduce((a, b) => (...args) => a(b(...args)));
          

        請(qǐng)問,chain(1) 輸出值?

        執(zhí)行順序?yàn)?sub3 -> div2 -> add5。(1 - 3) / 2 + 5 = 4。答案是 4。

        那么再問:

        const chain = [add5, div2, sub3].reduceRight((a, b) => (...args) => b(a(...args)));
          

        此時(shí) chain(1) 輸出值?還是 4。

        再看如下代碼:

        const chain = [add5, div2, sub3].reverse().reduce((a, b) => (...args) => b(a(...args)));
          

        此時(shí) chain(1) 輸出值?仍然是 4。

        如果你對(duì)上述示例都能很清晰的運(yùn)算出答案,那么你應(yīng)該對(duì)上文中 chain(next)的理解 ok,那么請(qǐng)繼續(xù)往下看。

        洋蔥模型機(jī)制

        nextChain("{data}");
          

        終于重頭戲來了,nextChain 函數(shù)來之不易,但毫無疑問,它的能力是十分強(qiáng)大的。(你看,其實(shí)在 redux 中,這個(gè) nextChain 函數(shù)其實(shí)就是 redux 中的 dispatch 函數(shù)。)

        文章截止目前為止,我們得知了 nextChain 函數(shù)即為 next_from_foo 函數(shù)。

        因此下述的執(zhí)行順序我將用函數(shù)堆棧圖給大家示意。

        img

        依次執(zhí)行,每當(dāng)執(zhí)行到 next 函數(shù)時(shí),新的 next 函數(shù)入棧,循環(huán)往復(fù),直到 next_from_baz 為止。函數(shù)入棧的過程,就相當(dāng)于進(jìn)行完了洋蔥模型從外到里的進(jìn)入過程。

        控制臺(tái)輸出日志:

        [fooMiddleware] before next
        [barMiddleware] before next
        [bazMiddleware] before next
          

        函數(shù)入棧直到最終的 next 函數(shù),我們知道,next 函數(shù)并沒有任何函數(shù)了,也就是說到達(dá)了終點(diǎn)。

        接下來就是逐層出棧。示意圖如下

        img

        控制臺(tái)輸出日志:

        [next] {data}
        [bazMiddleware] after next
        [barMiddleware] after next
        [fooMiddleware] after next
          

        函數(shù)出棧的過程,就相當(dāng)于洋蔥模型從里到外的出去過程。

        上述是函數(shù)堆棧的執(zhí)行順序。而下述示意圖是我整理后幫助大家理解的線性執(zhí)行順序。每當(dāng)執(zhí)行到 next(action)的時(shí)候函數(shù)入棧,原 next 函數(shù)暫時(shí)停止執(zhí)行,執(zhí)行新的 next 函數(shù),正如下圖彎曲箭頭所指。

        img

        上圖,代碼從上至下運(yùn)行,實(shí)際上就是調(diào)用棧的一個(gè)程序控制流程。所以理論上無論有多少個(gè)函數(shù)嵌套,都可以等同理解。

        我們修改一開始的洋蔥模型,示例如下:

        img

        小結(jié)

        redux 的中間件也就是比上述示例的中間件多了一層高階函數(shù)用以獲取框架內(nèi)部的 store。

        const reduxMiddleware = store => next => action => {
          // ...
          next(action);
          // ...
        };
          

        而 koa 的中間件多了 ctx 上下文參數(shù),和支持異步。

        app.use(async (ctx, next) => {
          // ...
          await next();
          // ...
        });
          

        你能想到大致如何實(shí)現(xiàn)了么?是不是有點(diǎn)撥開云霧見太陽的感覺了?

        如果有,本文發(fā)揮了它的作用和價(jià)值,筆者將會(huì)不甚榮幸。如果沒有,那筆者的表達(dá)能力還是有待加強(qiáng)。

        1. JavaScript 重溫系列(22篇全)
        2. ECMAScript 重溫系列(10篇全)
        3. JavaScript設(shè)計(jì)模式 重溫系列(9篇全)
        4. 正則 / 框架 / 算法等 重溫系列(16篇全)
        5. Webpack4 入門(上)|| Webpack4 入門(下)
        6. MobX 入門(上) ||  MobX 入門(下)
        7. 120+篇原創(chuàng)系列匯總

        回復(fù)“加群”與大佬們一起交流學(xué)習(xí)~

        點(diǎn)擊“閱讀原文”查看 120+ 篇原創(chuàng)文章

        瀏覽 85
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            老鸭窝av免费入口在线观看| 欧美AAA在线观看| 亚洲最新AV网站| 大香蕉精品一区| 88AV视频| 青娱乐青青草| 国产成人精品一区二区三区在线 | 亚洲精品无码久久| 人人干干| 黄色成人网址| 肏逼综合网| 嫩BBB搡BBB搡BBB四川| 中文字幕免费一区| 亚州精品国产精品乱码不99勇敢| 91成人免费视频| 久久综合99| 午夜不卡视频| 日本少妇BBW| 日本欧美在线视频| 欧美生活片18| 国产亚洲无码激情前后夹击| 亚洲欧美日本在线| 中文字幕视频在线免费观看| 色哥网在线一区| 久久婷婷秘精品国产538| 婷婷五月天综合网| 欧洲成人免费视频| 一级内射视频| 国产黄色视频在线免费看| 一区二区三区电影网| 亚洲欧美在线视频观看| 一级片在线免费看| 国产午夜福利免费视频在线观看| 精品福利一区二区三区| 五月天青青草超碰免费公开在线观看 | 日韩无码福利| 欧美成人在线免费| 国产精品国产三级片| 婷婷深爱五月丁香网| 亚洲小说图片AV在线| 亚洲中文av| 色欲av在线| 日韩在线毛片| 99热自拍| 国产免费一区二区| 女人操逼| 99这里有精品视频| 做爰视频毛片蜜桃| 一本色综合亚洲精品| 大香蕉免费在线观看| 桃花岛tⅴ+亚洲品质| 久干妞| 国产亚洲无码激情前后夹击| 久久精品视频免费看| 成人动漫免费观看| 在线看一区二区三区| 亚洲俺去了| 亚洲激情偷拍| JlZZJLZZJlZZ亚洲女人17| 撸撸操在线视频观看只有精品 | 少妇厨房愉情理伦BD在线观看 | 久久久国产AV| 我要看黄色一级片| 人人操人人妻人人| 婷婷激情六月| AV电影天堂网| 亚洲欧美另类图片| 99热在线中文字幕| 日韩极品视频在线| 强开小嫩苞毛片一二三区| 先锋影音亚洲AV每日资源网站| 欧美久久一区二区三区四区视频| 国产av探花| 翔田千里被躁120分钟| 色香蕉视频在线观看| 午夜无码在线观看视频| 99热精品免费在线观看| 色骚爽大香蕉91| 日韩美女免费性爱视频| 日日夜夜爽歪歪| 玖玖爱AV| 久久免费操| 91无码视频在线观看| 国产V精品| 中文字幕视频一区| 日本一级理论片在线大全| 国产乱伦熟女| www.av免费| 在线播放日韩| 日韩欧美国产成人| 色老板av| 色综合99久久久无码国产精品| 操久在线| 人人妻日日摸狠狠躁视频| 北岛玲丝袜办公室高跟| 91色色色色| 成人777777免费视频色| 黄色av免费网站| 日日搔AV一区二区三区| 日本一级片| 影音先锋国产av| 亚洲激情内射| 波多野结衣久久中文字幕| 亚洲爱| 伊人中文在线| 国产精品人妻AⅤ在线看| 人人操在线观看| 91久久婷婷| 清清草视频| 免费看a| 日本a视频| 人妻电影亚洲av| 国产婷婷色一区二区| www.av免费| AV电影天堂网| 在线观看中文字幕网站| 黄色一级大片在线免费看国产| 加勒比无码视频| 亚洲天堂在线看| 午夜成人福利剧场| a免费观看| 18禁网站免费| 国产高清一区二区三区| 丁香五月激情视频| 蜜臀久久99精品久久久老牛影视| 中文字幕无码人妻| 91在线免费视频| 国产精品国内自产拍| 日韩无码人妻系列| 男人的天堂婷婷| 亚洲欧洲日本在线| 另类老妇奶性BBWBBwBBw| 国产午夜成人免费看片无遮挡| 俺也去五月婷婷| 97精品国产97久久久久久免费| 超碰在线大香蕉| 国产在线观看| 国产毛片在线| 国产系列每日更新| 欧美成人aaa| 西西444WWW无码大胆在线观看| 91亚洲精华国产精华精华液| 日韩AV无码一区二区| 乱子伦国产精品视频一级毛| 在线A视频| AV天堂无码| 91久久国产性奴调教| 久久黄色视频网站| 欧美成人18| 国产精品一区网站| 久久高潮| 精品丰满人妻一区二区三区免费观| 人妻无码一区二区三区| 一区二区无码高清| 天天日天天射天天操| 亚洲av观看| 亚洲午夜福利| 亚洲乱码国产乱码精品天美传媒| 一级a一级a爰片免费免免在线| 啪啪网站免费看| 大香蕉在线网站| 久久久久无码精品国产91福利| 日本爱爱小视频| 亚洲WWW| 最新97色黄色精品高清网站| 色色丁香五月天| 成人性爱视频在线| 一级黄色免费片| 国产在线观看免费| 国产综合AV| 91嫖妓站街按摩店老熟女| 亚洲福利视频电影精| 国产精品视频你懂的| 黄色视频网站在线播放| 狠狠狠狠狠狠狠狠狠| 久久亚洲免费视频| 久操视频在线播放| 一区二区无码视频| 天天玩夜夜玩天天玩国产99| 欧美三级性爱视频| 免费黄色成人| 日韩亚洲天堂| 午夜高清无码视频| 超碰大香蕉| 日韩无码高清免费视频| 99视频+国产日韩欧美| 日本免费不卡视频| 影音先锋色先锋| 免费无码成人片在线播放| 精品蜜桃一区二区三区| 仙踪林777777野大粗| 欧美性受XXXX黑人XYX性爽一| AV无码一区二区三区| 四虎成人精品无码永久在线的客服 | 九九九久久久| 亚洲激情在线| 亚洲影音先锋资源| 北条麻妃久久久| 天天干天天日天天射| 狠狠干狠狠色| 一夲道无码专区av无码A片| 国产精品怡红院有限公司| 人妻无码精品蜜桃| 青青草原在线免费| 亚洲成人精品在线| 蜜臀久久99精品久久久老牛影视| а√天堂中文最新版8| 五月天久久久久久久| 欧美国产综合| 青青操网| 激情五月婷婷| 狠狠操一区| 狠狠噜噜| 国产精品揄拍500视频| jizz在线免费观看| 乱码中文字幕日韩欧美在线| 水果派解说AV无码一区| 欧性猛交ⅩXXX乱大交| 站街大龄熟女x| 色婷婷综合久久久中文字幕| 老司机AV91| 伊人色综合网| 国产老熟女久久久| 国产AⅤ爽aV久久久久成人| 亚洲无码电影在线观看| 怡红院麻豆| 精品国产va久久久久久久 | 高清国产AV| 国产叼嘿视频| 国内超碰| 99xxxxx| 精品免费国产一区二区三区四区的使用方法| 少妇高潮av久久久久久| 91社区成人影院| 日韩婬乱片A片AAA真人视频| 操操影视| 久久肏屄视频| 9999re| 欧美激情在线观看| 日本黄色视频免费观看| 亚洲91黄色片| 97人人爽人人爽人人爽人人爽| 欧美日一区二区三区| 国产高清一区| 亚洲国产A片| 日韩无码视屏| 99热超碰在线| 在线一区二区三区| 伊人啪啪| 婷婷午夜精品久久久久久| 中文二区| 国产一级美女操逼视频免费播放| 日韩在线综合网| 俺去也在线视频| 国产传媒自拍| 一级片黄色免费| 国产网站精品| AV黄色在线观看| 另类老妇奶性BBWBBw| 亚洲色一| 欧美成人毛片一级A片| 91工厂露脸熟女| 一本色道久久综合熟妇人妻| 中文字幕日本人妻| 自拍偷拍成人视频| 色就是色欧美成人网| 五月婷婷导航| 91精品国产三级| 韩剧《邻居的妻子》电视剧| 免费在线观看Av| 日韩在线精品视频| 99精品在线| 草草影院第一页YYCCC| 超碰天天爱| 91夫妻交友视频| 日韩日批视频| 俺去啦在线视频| 韩国中文字幕HD久久| 欧美性生交18XXXXX无码| 天堂在线中文| 特级毛片在线观看| 国产精品一色哟哟哟| 色天堂网| 大地8免费高清视频观看大全 | 在桌下含她的花蒂和舌头H视频| 亚洲精品成a人在线观看| 在线综合国产欧美| 69欧美视频| 九色精品| 亚洲色婷婷久久精品AV蜜桃| 我和岳m愉情XXXⅩ视频| 天天干视频| 色婷婷AV在线| 欧美精品日韩在线观看| 大鸡巴黄色视频免费观看| 三级A片视频| 一道本一区二区| 人妻精品一区二区在线| 成人午夜精品无码区| 黄色www|