欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

JavaScript?設(shè)計(jì)模式之洋蔥模型原理及實(shí)踐應(yīng)用

 更新時(shí)間:2022年09月03日 11:31:02   作者:gary  
這篇文章主要介紹了JavaScript?設(shè)計(jì)模式之洋蔥模型原理及實(shí)踐應(yīng)用,主要針對(duì)項(xiàng)目中遇到的問(wèn)題,引申到koa-compose原理解析。通過(guò)學(xué)習(xí)洋蔥模式來(lái)解決我們實(shí)際項(xiàng)目中的問(wèn)題

前言

先來(lái)聽(tīng)聽(tīng)一個(gè)故事吧,今天產(chǎn)品提了一個(gè)業(yè)務(wù)需求:用戶(hù)在一個(gè)編輯頁(yè)面,此時(shí)用戶(hù)點(diǎn)擊退出登錄,應(yīng)用需要提示用戶(hù)當(dāng)前有編輯內(nèi)容未保存,是否保存;當(dāng)用戶(hù)操作完畢后再提示用戶(hù)是否退出登錄。

流程如下:

因?yàn)橥顺龅卿浭菍儆诠膊糠钟闪硪晃煌瑢W(xué)維護(hù),此時(shí)和他交流后“善良”的把需求仍給了他。并告知他可以通過(guò)某某方法獲取我當(dāng)前是否有編輯內(nèi)容。然后我繼續(xù)摸魚(yú),他開(kāi)始瘋狂輸出

const handlerLogout = async () => {
    if (window.location.href === 'xxx') {
        if (getEditState() === 'xxx') {
            await editConfirm()
        }
    }
    await logoutConfirm();
}

功能如約上線(xiàn),新需求也如約到達(dá):產(chǎn)品期望用戶(hù)在VIP充值頁(yè)面退出登錄的時(shí)候,先彈出一個(gè)VIP充值廣告,當(dāng)用戶(hù)關(guān)閉廣告后再提示用戶(hù)是否退出登錄。

流程如下:

然后熟悉的場(chǎng)景、熟悉的人,在一番交流過(guò)后,那位同學(xué)略微暴躁的又開(kāi)始瘋狂輸出,然后我繼續(xù)摸魚(yú)

const pages = {
    editPage: async () => {
        if (getEditState() === 'xxx') {
            await editConfirm()
        }
    },
    vipPage: async () => {
        if (getUserVipState() === 'xxx') {
            await vipConfirm()
        }
    }
}
const handlerLogout = async () => {
    const curPage = getPage();
    await pages[curPage];
    await logoutConfirm();
}

然后的然后功能又如約上線(xiàn),然后需求又來(lái)了,一個(gè)場(chǎng)景中有多個(gè)彈窗業(yè)務(wù),優(yōu)先級(jí)不同,如果彈窗1不滿(mǎn)足彈出條件,就使用彈窗2依此類(lèi)推。眾所周知產(chǎn)品的需求怎么做的完,他終于受不了了,開(kāi)始思考怎么樣自己才能摸摸魚(yú)。與似乎不好的想法油然而生,如果自己維護(hù)的退出登錄就只關(guān)注處理退出登錄的業(yè)務(wù),而其他業(yè)務(wù)的各種彈窗讓業(yè)務(wù)方自己去處理那我就可以摸魚(yú)啦。想法有了,拆解一下邏輯,底層邏輯就是在觸發(fā)時(shí)需要有很多中間層的處理,等中間層處理完成后再處理自己的。那這不就像是洋蔥模型嗎。

洋蔥模型

提到洋蔥模型,koa的實(shí)現(xiàn)簡(jiǎn)單且優(yōu)雅。koa中主要使用koa-compose來(lái)實(shí)現(xiàn)該模式。核心內(nèi)容只有十幾行,但是卻涉及到高階函數(shù)、閉包、遞歸、尾調(diào)用優(yōu)化等知識(shí),不得不說(shuō)非常驚艷沒(méi)有一行是多余的。簡(jiǎn)單來(lái)說(shuō),koa-compose暴露出一個(gè)compose方法,該方法接受一個(gè)中間件數(shù)組,并返回一個(gè)Promise函數(shù)。源碼如下

function compose (middleware) {
  if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!')
  for (const fn of middleware) {
    if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!')
  }
  /**
   * @param {Object} context
   * @return {Promise}
   * @api public
   */
  return function (context, next) {
    // last called middleware #
    let index = -1
    return dispatch(0)
    function dispatch (i) {
      if (i <= index) return Promise.reject(new Error('next() called multiple times'))
      index = i
      let fn = middleware[i]
      if (i === middleware.length) fn = next
      if (!fn) return Promise.resolve()
      try {
        return Promise.resolve(fn(context, dispatch.bind(null, i + 1)))
      } catch (err) {
        return Promise.reject(err)
      }
    }
  }
}

源碼中compose主要做了三件事

  • 第一步:進(jìn)行入?yún)⑿r?yàn)
  • 第二步:返回一個(gè)函數(shù),并利用閉包保存middleware和index的值
  • 第三步:調(diào)用時(shí),執(zhí)行dispatch(0),默認(rèn)從第一個(gè)中間件執(zhí)行

dispatch函數(shù)的作用(dispatch其實(shí)就是next函數(shù))

  • 第一步:通過(guò)i <= index來(lái)避免在同一個(gè)中間件中連續(xù)next調(diào)用
  • 第二步:設(shè)置index的值為當(dāng)前中間件位置的值,并且拿到當(dāng)前中間件函數(shù)
  • 第三步:判斷當(dāng)前是否還有中間件,沒(méi)有返回Promise.resolve()
  • 第四步:返回Promise.resolve并把當(dāng)前中間件執(zhí)行結(jié)果做為返回,且傳入context和next(dispatch)方法。這里利用尾調(diào)優(yōu)化,避免了fn重新創(chuàng)建新的棧幀,同時(shí)提升了速度和節(jié)省了內(nèi)存(大佬就是大佬)

我們可以通過(guò)其測(cè)試用例了解到執(zhí)行的過(guò)程,有條件的讀者可以通過(guò)下載源碼進(jìn)行斷點(diǎn)調(diào)試,更能理解每一步的過(guò)程

  it('should work', async () => {
    const arr = []
    const stack = []
    stack.push(async (context, next) => {
      arr.push(1) // 步驟1
      await wait(1) // 步驟2
      await next() //  步驟3
      await wait(1) // 步驟14
      arr.push(6) // 步驟15
    })
    stack.push(async (context, next) => {
      arr.push(2) // 步驟4
      await wait(1) // 步驟5
      await next() // 步驟6
      await wait(1) // 步驟12
      arr.push(5) // 步驟13
    })
    stack.push(async (context, next) => {
      arr.push(3) // 步驟7
      await wait(1) // 步驟8
      await next() // 步驟9
      await wait(1) // 步驟10
      arr.push(4) // 步驟11
    })
    await compose(stack)({})
    expect(arr).toEqual(expect.arrayContaining([1, 2, 3, 4, 5, 6]))
  })

compose接收一個(gè)參數(shù),該參數(shù)是一個(gè)Promise數(shù)組,注入中間件后返回了一個(gè)執(zhí)行函數(shù)并執(zhí)行。此時(shí)會(huì)按照上訴我標(biāo)記的步驟進(jìn)行執(zhí)行。配置koa文檔中的gif示例和流程圖更好理解。通過(guò)不斷的遞歸加上Promise鏈?zhǔn)秸{(diào)用完成了整個(gè)中間件的執(zhí)行

實(shí)踐

已經(jīng)了解到洋蔥模型的設(shè)計(jì),按照當(dāng)前摸魚(yú)的訴求,期望stack.push這部分內(nèi)容由業(yè)務(wù)方自己去注入,而退出登錄只需要執(zhí)行compose(stack)({})即可,額外訴求是項(xiàng)目中期望對(duì)彈窗有優(yōu)先級(jí)的處理,那就是不是誰(shuí)先進(jìn)入誰(shuí)先執(zhí)行。對(duì)此改造一下middleware定義,新增level表示優(yōu)先級(jí)后續(xù)它進(jìn)行排序,優(yōu)先級(jí)越高設(shè)置level值越高即可。

type Middleware<T = unknown> = {
  level: number;
  middleware: (context: T | undefined, next: () => Promise<any>) => void;
};

因?yàn)槲覀冃枰峁┙o業(yè)務(wù)方一個(gè)接口來(lái)添加中間件,這里使用類(lèi)來(lái)實(shí)現(xiàn),通過(guò)暴露出add和remove方法對(duì)中間件進(jìn)行添加和刪除,利用add方法在添加時(shí)利用level對(duì)中間件進(jìn)行排序,使用stack來(lái)保存已經(jīng)排序好的中間件。dispatch通過(guò)CV大法實(shí)現(xiàn)

class Scheduler<T> {
  stack: Middleware<T>[] = [];
  add(middleware: Middleware<T>) {
    const index = this.stack.findIndex((it) => it.level <= middleware.level);
    this.stack.splice(index === -1 ? this.stack.length : index, 0, middleware);
    return () => {
      this.remove(middleware);
    };
  }
  remove(middleware: Middleware<T>) {
    const index = this.stack.findIndex((it) => it === middleware);
    index > -1 && this.stack.splice(index, 1);
  }
  dispatch(context?: T) {
    // eslint-disable-next-line
    const that = this;
    let index = -1;
    return mutate(0);
    function mutate(i: number): Promise<void> {
      if (i <= index) return Promise.reject(new Error('next() called multiple times'));
      index = i;
      const fn = that.stack[i];
      if (index === that.stack.length) return Promise.resolve();
      try {
        return Promise.resolve(fn.middleware(context, mutate.bind(null, i + 1)));
      } catch (error) {
        return Promise.reject(error);
      }
    }
  }
}
export default Scheduler;

然后修改業(yè)務(wù)中的處理,之后再加類(lèi)似需求就可以摸魚(yú)了。

// 暴露一個(gè)logoutScheduler方法
export const logoutScheduler = new Scheduler();
const handleLogout = () => {
    logoutScheduler.dispatch().then(() => {
        logoutConfirm();
    })
}
// 編輯頁(yè)面
logoutScheduler.add({
    level: 2,
    middleware: async (_, next) => {
        if (getEditState() === 'xxx') {
          await editConfirm()
        }
        await next();
    }
})
// vip頁(yè)面
logoutScheduler.add({
    level: 2,
    middleware: async (_, next) => {
        if (getUserVipState() === 'xxx') {
            await vipConfirm()
        }
        await next();
    }
})

總結(jié)

一個(gè)好的設(shè)計(jì)能在實(shí)際開(kāi)發(fā)中更好的去解耦業(yè)務(wù),而好的設(shè)計(jì)需要我們?nèi)ラ喿x那些優(yōu)秀的源碼去學(xué)習(xí)和理解才能為我們所用。

以上就是JavaScript 設(shè)計(jì)模式之洋蔥模型原理及實(shí)踐應(yīng)用的詳細(xì)內(nèi)容,更多關(guān)于JavaScript 設(shè)計(jì)模式洋蔥模型的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評(píng)論