日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

面试官问发布订阅模式是在问什么?

發布時間:2023/12/9 编程问答 25 豆豆
生活随笔 收集整理的這篇文章主要介紹了 面试官问发布订阅模式是在问什么? 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

大家好,我是若川。最近組織了源碼共讀活動,感興趣的可以加我微信 ruochuan12 參與,已進行了三個多月,大家一起交流學習,共同進步。

本文來自 @simonezhou 小姐姐投稿的第八期筆記。面試官常問發布訂閱、觀察者模式,我們日常開發也很常用。文章講述了 mitt、tiny-emitter、Vue eventBus這三個發布訂閱、觀察者模式相關的源碼。

源碼地址

  • mitt:https://github.com/developit/mitt

  • tiny-emitter:https://github.com/scottcorgan/tiny-emitter

  • 1. mitt 源碼解讀

    1.1 package.json 項目 build 打包(運用到包暫不深究,保留個印象即可)

    執行 npm run build:

    //? "scripts":?{..."bundle":?"microbundle?-f?es,cjs,umd","build":?"npm-run-all?--silent?clean?-p?bundle?-s?docs","clean":?"rimraf?dist","docs":?"documentation?readme?src/index.ts?--section?API?-q?--parse-extension?ts",...},
    • 使用 npm-run-all(A CLI tool to run multiple npm-scripts in parallel or sequential:https://www.npmjs.com/package/npm-run-all) 命令執行

    • clean 命令,使用 rimraf(The UNIX command rm -rf for node. https://www.npmjs.com/package/rimraf)刪除 dist 文件路徑

    • bundle 命令,使用 microbundle(The zero-configuration bundler for tiny modules, powered by Rollup. https://www.npmjs.com/package/microbundle) 進行打包

    • microbundle 命令指定 format: es, cjs, umd, ?package.json 指定 soucre 字段為打包入口 js:


    {"name":?"mitt",??????????//?package?name......"module":?"dist/mitt.mjs",????//?ES?Modules?output?bundle"main":?"dist/mitt.js",??????//?CommonJS?output?bundle"jsnext:main":?"dist/mitt.mjs",???//?ES?Modules?output?bundle"umd:main":?"dist/mitt.umd.js",??//?UMD?output?bundle"source":?"src/index.ts",?????//?input"typings":?"index.d.ts",?????//?TypeScript?typings?directory"exports":?{"import":?"./dist/mitt.mjs",????//?ES?Modules?output?bundle"require":?"./dist/mitt.js",??//?CommonJS?output?bundle"default":?"./dist/mitt.mjs"??//?Modern?ES?Modules?output?bundle},... }

    1.2 如何調試查看分析?

    使用 microbundle watch 命令,新增 script,執行 npm run dev:

    "dev":?"microbundle?watch?-f?es,cjs,umd"

    對應目錄新增入口,比如 test.js,執行 node test.js 測試功能:

    const?mitt?=?require('./dist/mitt');const?Emitter?=?mitt();Emitter.on('test',?(e,?t)?=>?console.log(e,?t));Emitter.emit('test',?{?a:?12321?});

    對應源碼 src/index.js 也依然可以加相關的 log 進行查看,代碼變動后會觸發重新打包

    1.3. TS 聲明

    使用上可以(官方給的例子),比如定義 foo 事件,回調函數里面的參數要求是 string 類型,可以想象一下源碼 TS 是怎么定義的:

    import?mitt?from?'mitt';//?key?為事件名,key?對應屬性為回調函數的參數類型? type?Events?=?{foo:?string;bar?:?number;?//?對應事件允許不傳參數 };const?emitter?=?mitt<Events>();?//?inferred?as?Emitter<Events>emitter.on('foo',?(e)?=>?{});?//?'e'?has?inferred?type?'string'emitter.emit('foo',?42);?//?Error:?Argument?of?type?'number'?is?not?assignable?to?parameter?of?type?'string'.?(2345)emitter.on('*',?(type,?e)?=>?console.log(type,?e)?)

    源碼內關于 TS 定義(關鍵幾句):

    export?type?EventType?=?string?|?symbol;//?Handler?為事件(除了*事件)回調函數定義 export?type?Handler<T?=?unknown>?=?(event:?T)?=>?void;//?WildcardHandler?為事件?*?回調函數定義 export?type?WildcardHandler<T?=?Record<string,?unknown>>?=?(type:?keyof?T,???//?keyof?T,事件名event:?T[keyof?T]??//?T[keyof?T],?事件名對應的回調函數入參類型 )?=>?void;export?interface?Emitter<Events?extends?Record<EventType,?unknown>>?{//?...on<Key?extends?keyof?Events>(type:?Key,?handler:?Handler<Events[Key]>):?void;on(type:?'*',?handler:?WildcardHandler<Events>):?void;//?...emit<Key?extends?keyof?Events>(type:?Key,?event:?Events[Key]):?void;//?這句主要兼容無參數類型的事件,如果說事件對應回調必須傳參,使用中如果未傳,那么會命中?never,如下圖emit<Key?extends?keyof?Events>(type:?undefined?extends?Events[Key]???Key?:?never):?void; }

    以下是會報 TS 錯誤:

    以下是正確的:

    1.4 主邏輯

  • 整體就是一個 function,輸入為事件 Map,輸出為 all 所有事件 Map,還有 on,emit,off 幾個關于事件方法:

  • export?default?function?mitt<Events?extends?Record<EventType,?unknown>>(//?支持?all?初始化all?:?EventHandlerMap<Events> ):?Emitter<Events>?{//?內部維護了一個?Map(all),Key?為事件名,Value?為?Handler?回調函數數組all?=?all?||?new?Map();return?{all,???//?所有事件?&?事件對應方法emit,??//?觸發事件on,???//?訂閱事件off???//?注銷事件} }
  • on 為【事件訂閱】,push 對應 Handler 到對應事件 Map 的 Handler 回調函數數組內(可熟悉下 Map 相關API https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Map):

  • on<Key?extends?keyof?Events>(type:?Key,?handler:?GenericEventHandler)?{//?Map?get?獲取const?handlers:?Array<GenericEventHandler>?|?undefined?=?all!.get(type);//?如果已經初始化過的話,是個數組,直接?push?即可if?(handlers)?{handlers.push(handler);}//?如果第一次注冊事件,則?set?新的數組else?{all!.set(type,?[handler]?as?EventHandlerList<Events[keyof?Events]>);} }
  • off 為【事件注銷】,從對應事件 Map 的 Handlers 中,splice 掉:

  • off<Key?extends?keyof?Events>(type:?Key,?handler?:?GenericEventHandler)?{//?Map?get?獲取const?handlers:?Array<GenericEventHandler>?|?undefined?=?all!.get(type);//?如果有事件列表,則進入,沒有則忽略if?(handlers)?{//?對?handler?事件進行?splice?移出數組//?這里是對找到的第一個?handler?進行移出,所以如果訂閱了多次,只會去除第一個//?handlers.indexOf(handler)?>>>?0,>>>?為無符號位移//?關于網上對?>>>?用法說明:It doesn't just convert non-Numbers to Number, it converts them to Numbers that can be expressed as 32-bit unsigned ints.if?(handler)?{handlers.splice(handlers.indexOf(handler)?>>>?0,?1);}//?如果不傳對應的?Handler,則為清空事件對應的所有訂閱else?{all!.set(type,?[]);}} }
  • emit 為【事件觸發】,讀取事件 Map 的 Handlers,循環逐一觸發,如果訂閱了 * 全事件,則讀取 * 的 Handlers 逐一觸發:

  • emit<Key?extends?keyof?Events>(type:?Key,?evt?:?Events[Key])?{//?獲取對應?type?的?Handlerslet?handlers?=?all!.get(type);if?(handlers)?{(handlers?as?EventHandlerList<Events[keyof?Events]>).slice().map((handler)?=>?{handler(evt!);});}//?獲取?*?對應的?Handlershandlers?=?all!.get('*');if?(handlers)?{(handlers?as?WildCardEventHandlerList<Events>).slice().map((handler)?=>?{handler(type,?evt!);});} }

    為什么是使用 slice().map() ,而不是直接使用 forEach() 進行觸發?具體可查看:https://github.com/developit/mitt/pull/109,具體可以拷貝相關代碼進行調試,直接更換成 forEach 的話,針對以下例子所觸發的 emit 是錯誤的:

    import?mitt?from?'./mitt'type?Events?=?{test:?number }const?Emitter?=?mitt<Events>() Emitter.on('test',?function?A(num)?{console.log('A',?num)Emitter.off('test',?A) }) Emitter.on('test',?function?B()?{console.log('B') }) Emitter.on('test',?function?C()?{console.log('C') })Emitter.emit('test',?32432)?//?觸發?A,C?事件,B?會被漏掉 Emitter.emit('test',?32432)?//?觸發?B,C,這個是正確的//?原因解釋: //?forEach?時,在?Handlers?循環過程中,同時觸發了?off?操作 //?按這個例子的話,A?是第一個被注冊的,所以第一個會被?slice?掉 //?因為?array?是引用類型,slice?之后,那么?B?函數就會變成第一個 //?但此時遍歷已經到第二個了,所以?B?函數就會被漏掉執行//?解決方案: //?所以對數組進行?[].slice()?做一個淺拷貝,off?的?Handlers?與?當前循環中的?Handlers?處理成不同一個 //?[].slice.forEach()?效果其實也是一樣的,用?map?的話個人感覺不是很語義化

    1.5 小結

    • TS keyof 的靈活運用

    • undefined extends Events[Key] ? Key : never,為 TS 的條件類型(https://www.typescriptlang.org/docs/handbook/2/conditional-types.html)

    • undefined extends Events[Key] ? Key : never,當我們想要編譯器不捕獲當前值或者類型時,我們可以返回 never類型。never 表示永遠不存在的值的類型

    //?來自?typescript?中的?lib.es5.d.ts?定義/***?Exclude?null?and?undefined?from?T*/ type?NonNullable<T>?=?T?extends?null?|?undefined???never?:?T;//?如果?T?的值包含?null?或者?undefined,則會?never?表示不允許走到此邏輯,否則返回?T?本身的類型
    • mitt 的事件回調函數參數,只會有一個,而不是多個,如何兼容多個參數的情況,官方推薦是使用 object 的(object is recommended and powerful),這種設計擴展性更高,更值得推薦。

    2. tiny-emitter 源碼解讀

    2.1 主邏輯

  • 所有方法都是掛載在 E 的 prototype 內的,總共暴露了 once,emit,off,on 四個事件的方法:

  • function?E?()?{//?Keep?this?empty?so?it's?easier?to?inherit?from//?(via?https://github.com/lipsmack?from?https://github.com/scottcorgan/tiny-emitter/issues/3) }//?所有事件都掛載在?this.e?上,是個?object E.prototype?=?{on:?function?(name,?callback,?ctx)?{},once:?function?(name,?callback,?ctx)?{},emit:?function?(name)?{},off:?function?(name,?callback)?{} }module.exports?=?E; module.exports.TinyEmitter?=?E;
  • once 訂閱一次事件,當被觸發一次后,就會被銷毀:

  • once:?function?(name,?callback,?ctx)?{var?self?=?this;//?構造另一個回調函數,調用完之后,銷毀該?callbackfunction?listener?()?{self.off(name,?listener);?????//?銷毀callback.apply(ctx,?arguments);??//?執行};listener._?=?callback//?on?函數返回?this,所以可以鏈式調用return?this.on(name,?listener,?ctx);?//?訂閱這個構造的回調函數 }
  • on 事件訂閱

  • on:?function?(name,?callback,?ctx)?{var?e?=?this.e?||?(this.e?=?{});//?單純?push?進去,這里也沒有做去重,所以同一個回調函數可以被訂閱多次(e[name]?||?(e[name]?=?[])).push({fn:?callback,ctx:?ctx});//?返回?this,可以鏈式調用return?this; }
  • off 事件銷毀

  • off:?function?(name,?callback)?{var?e?=?this.e?||?(this.e?=?{});var?evts?=?e[name];var?liveEvents?=?[];?//?保存還有效的?hanlder//?傳遞的?callback,如果命中,就不會被放到?liveEvents?里面//?所以這里的銷毀是一次性銷毀全部相同的?callback,與?mitt?不一樣if?(evts?&&?callback)?{for?(var?i?=?0,?len?=?evts.length;?i?<?len;?i++)?{if?(evts[i].fn?!==?callback?&&?evts[i].fn._?!==?callback)liveEvents.push(evts[i]);}}//?Remove?event?from?queue?to?prevent?memory?leak//?Suggested?by?https://github.com/lazd//?Ref:?https://github.com/scottcorgan/tiny-emitter/commit/c6ebfaa9bc973b33d110a84a307742b7cf94c953#commitcomment-5024910//?如果沒有任何?handler,對應的事件?name?也可以被?delete(liveEvents.length)??e[name]?=?liveEvents:?delete?e[name];//?返回?this,可以鏈式調用return?this; }
  • emit 事件觸發

  • emit:?function?(name)?{//?取除了第一位的剩余所有參數var?data?=?[].slice.call(arguments,?1);// slice()?淺拷貝var?evtArr?=?((this.e?||?(this.e?=?{}))[name]?||?[]).slice();var?i?=?0;var?len?=?evtArr.length;//?循環逐個觸發?handler,把?data?傳入其中for?(i;?i?<?len;?i++)?{evtArr[i].fn.apply(evtArr[i].ctx,?data);}//?返回?this,可以鏈式調用return?this; }

    2.2 小結

    • return this,支持鏈式調用

    • emit 事件觸發時,[].slice.call(arguments, 1) 剔除第一個參數,獲取到剩余的參數列表,再使用 apply 來調用

    • on 事件訂閱時,記錄的是 { fn, ctx },fn 為回調函數,ctx 支持綁定上下文

    3. mitt 與 tiny-emitter 對比

    • TS 靜態類型校驗上 mitt > tiny-emitter,開發更友好,對于回調函數參數的管理,tiny-emitter 支持多參數調用的,但是 mitt 提倡使用 object 管理,設計上感覺 mitt 更加友好以及規范

    • 在 off 事件銷毀中,tiny-emitter 與 mitt 處理方式不同,tiny-emitter 會一次性銷毀所有相同的 callback,而 mitt 則只是銷毀第一個

    • mitt 不支持 once 方法,tiny-emitter 支持 once 方法

    • mitt 支持 * 全事件訂閱,tiny-emitter 則不支持

    4. Vue eventBus 事件總線(3.x 已廢除,2.x 依然存在)

    • 關于 events 的處理:https://github.com/vuejs/vue/blob/dev/src/core/instance/events.js

    • 事件相關初始化:https://github.com/vuejs/vue/blob/dev/src/core/instance/index.js

  • 初始化過程

  • //?index.js?調用?initMixin?方法,初始化?_events?object initMixin(Vue)//?event.js?定義?initEvents?方法 //?vm._events?保存所有事件?&?事件回調函數,是個?object export?function?initEvents?(vm:?Component)?{vm._events?=?Object.create(null)//?... }//?index.js?調用?eventsMixin,往?Vue.prototype?掛載相關事件方法 eventsMixin(Vue)//?event.js?定義了?eventsMixin?方法 export?function?eventsMixin?(Vue:?Class<Component>)?{//?事件訂閱Vue.prototype.$on?=?function?(event:?string?|?Array<string>,?fn:?Function):?Component?{}//?事件訂閱執行一次Vue.prototype.$once?=?function?(event:?string,?fn:?Function):?Component?{}//?事件退訂Vue.prototype.$off?=?function?(event?:?string?|?Array<string>,?fn?:?Function):?Component?{}//?事件觸發Vue.prototype.$emit?=?function?(event:?string):?Component?{} }
  • $on 事件訂閱

  • //?event?是個?string,也可以是個?string?數組 //?說明可以一次性對多個事件,訂閱同一個回調函數 Vue.prototype.$on?=?function?(event:?string?|?Array<string>,?fn:?Function):?Component?{const?vm:?Component?=?thisif?(Array.isArray(event))?{for?(let?i?=?0,?l?=?event.length;?i?<?l;?i++)?{vm.$on(event[i],?fn)}}?else?{//?本質是就是對應?event,push?對應的?fn(vm._events[event]?||?(vm._events[event]?=?[])).push(fn)//?以下先不展開,關于?hookEvent?的調用說明//?optimize?hook:event?cost?by?using?a?boolean?flag?marked?at?registration//?instead?of?a?hash?lookupif?(hookRE.test(event))?{vm._hasHookEvent?=?true}}return?vm }
  • $once 事件訂閱&執行一次

  • //?包裝一層?on,內包含退訂操作以及調用操作 //?訂閱的是包裝后的?on?回調函數 Vue.prototype.$once?=?function?(event:?string,?fn:?Function):?Component?{const?vm:?Component?=?thisfunction?on?()?{vm.$off(event,?on)fn.apply(vm,?arguments)}on.fn?=?fnvm.$on(event,?on)return?vm }
  • $off 事件退訂

  • Vue.prototype.$off?=?function?(event?:?string?|?Array<string>,?fn?:?Function):?Component?{const?vm:?Component?=?this//?沒有傳參數,說明全部事件退訂,直接清空if?(!arguments.length)?{vm._events?=?Object.create(null)return?vm}//?存在?event?數組,遍歷逐一調用自己if?(Array.isArray(event))?{for?(let?i?=?0,?l?=?event.length;?i?<?l;?i++)?{vm.$off(event[i],?fn)}return?vm}//?以下情況為非數組事件名,為單一事件,則獲取該事件對應訂閱的?callbacksconst?cbs?=?vm._events[event]//?若?callbacks?為空,什么都不用做if?(!cbs)?{return?vm}//?如果傳入的?fn?為空,說明退訂這個事件的所有?callbacksif?(!fn)?{vm._events[event]?=?nullreturn?vm}//?callbacks?不為空,并且?fn?不為空,則為退訂某個?callbacklet?cblet?i?=?cbs.lengthwhile?(i--)?{cb?=?cbs[i]//?訂閱多次的?callback,都會被退訂,一次退訂所有相同的?callbackif?(cb?===?fn?||?cb.fn?===?fn)?{cbs.splice(i,?1)break}}return?vm }
  • $emit 事件觸發

  • Vue.prototype.$emit?=?function?(event:?string):?Component?{const?vm:?Component?=?thisif?(process.env.NODE_ENV?!==?'production')?{const?lowerCaseEvent?=?event.toLowerCase()if?(lowerCaseEvent?!==?event?&&?vm._events[lowerCaseEvent])?{tip(`Event?"${lowerCaseEvent}"?is?emitted?in?component?`?+`${formatComponentName(vm)}?but?the?handler?is?registered?for?"${event}".?`?+`Note?that?HTML?attributes?are?case-insensitive?and?you?cannot?use?`?+`v-on?to?listen?to?camelCase?events?when?using?in-DOM?templates.?`?+`You?should?probably?use?"${hyphenate(event)}"?instead?of?"${event}".`)}}//?獲取這個?event?的?callbacks?出來let?cbs?=?vm._events[event]if?(cbs)?{cbs?=?cbs.length?>?1???toArray(cbs)?:?cbs//?獲取除了第一位,剩余的其他所有參數const?args?=?toArray(arguments,?1)const?info?=?`event?handler?for?"${event}"`//?遍歷逐一觸發for?(let?i?=?0,?l?=?cbs.length;?i?<?l;?i++)?{//?以下暫不展開,這是?Vue?中對于方法調用錯誤異常的處理方案invokeWithErrorHandling(cbs[i],?vm,?args,?vm,?info)}}return?vm }

    實現邏輯大致和 mitt,tiny-emitter 一致,也是 pubsub,整體思路都是維護一個 object 或者 Map,on 則是放到數組內,emit 則是循環遍歷逐一觸發,off 則是查找到對應的 handler 移除數組
    TODO:

    • Vue 中對于方法調用錯誤異常的處理方案:invokeWithErrorHandling

    • hookEvent 的使用&原理

    5. 附錄

    • rimraf:https://www.npmjs.com/package/rimraf

    • microbundle:https://www.npmjs.com/package/microbundle

    • package.json exports 字段:https://nodejs.org/api/packages.html#packages_conditional_exports

    • Map:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Map

    • TS 條件類型:https://www.typescriptlang.org/docs/handbook/2/conditional-types.html

    • TS Never:https://www.typescriptlang.org/docs/handbook/basic-types.html#never

    • TS keyof: https://www.typescriptlang.org/docs/handbook/2/keyof-types.html#the-keyof-type-operator

    • What is the JavaScript >>> operator and how do you use it? https://stackoverflow.com/questions/1822350/what-is-the-javascript-operator-and-how-do-you-use-it


    最近組建了一個江西人的前端交流群,如果你是江西人可以加我微信?ruochuan12?私信 江西?拉你進群。

    推薦閱讀

    1個月,200+人,一起讀了4周源碼
    我歷時3年才寫了10余篇源碼文章,但收獲了100w+閱讀

    老姚淺談:怎么學JavaScript?

    我在阿里招前端,該怎么幫你(可進面試群)

    ·················?若川簡介?·················

    你好,我是若川,畢業于江西高校。現在是一名前端開發“工程師”。寫有《學習源碼整體架構系列》10余篇,在知乎、掘金收獲超百萬閱讀。
    從2014年起,每年都會寫一篇年度總結,已經寫了7篇,點擊查看年度總結。
    同時,最近組織了源碼共讀活動,幫助1000+前端人學會看源碼。公眾號愿景:幫助5年內前端人走向前列。

    識別方二維碼加我微信、拉你進源碼共讀

    今日話題

    略。歡迎分享、收藏、點贊、在看我的公眾號文章~

    創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎

    總結

    以上是生活随笔為你收集整理的面试官问发布订阅模式是在问什么?的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。