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>

        工作中如何巧妙的使用發(fā)布訂閱模式

        共 8918字,需瀏覽 18分鐘

         ·

        2021-01-11 10:53

        點(diǎn)擊上方“藍(lán)字”帶你每天閱讀全棧前端精選好文

        不同的語言-相同的模式

        最近在看設(shè)計(jì)模式的知識,而且在工作當(dāng)中,做一些打點(diǎn)需求的時(shí)候,正好直接利用了發(fā)布訂閱模式去實(shí)現(xiàn)的,這讓我對發(fā)布訂閱這種設(shè)計(jì)模式更加的感興趣了,于是借此機(jī)會(huì)也和大家說說這個(gè)好東東吧!

        其實(shí)在早期還是用jq開發(fā)的時(shí)代,有很多地方,我們都會(huì)出現(xiàn)發(fā)布訂閱的影子,例如有trigger和on方法

        再到現(xiàn)在的vue中,emit和on方法。他們都似乎不約而同的自帶了發(fā)布訂閱屬性一般,讓開發(fā)變得更加高效好用起來

        那么廢話不多說了,先來看看發(fā)布訂閱模式到底何方神圣吧

        發(fā)布訂閱模式

        說到發(fā)布訂閱模式,它其實(shí)是一種對象間一對多的依賴關(guān)系(不是綜藝節(jié)目以一敵百那種),當(dāng)一個(gè)對象的狀態(tài)發(fā)送改變時(shí),所有依賴于它的對象都將得到狀態(tài)改變的通知

        正所謂,字?jǐn)?shù)不多,不代表作用不大,那繼續(xù)來看下它的作用

        作用

        1. 廣泛應(yīng)用于異步編程中(替代了傳遞回調(diào)函數(shù))

        2. 對象之間松散耦合的編寫代碼

        就這兩點(diǎn)嗎?沒錯(cuò),點(diǎn)不在多,夠用就行。我們都知道有一句很著名的諺語,羅馬不是一天建成的

        當(dāng)然,胖子也不是一天吃成的。所以我們要想實(shí)現(xiàn)一個(gè)自己的發(fā)布訂閱模式,以后在工作中使用,也需要一點(diǎn)點(diǎn)來的,表捉急,先從最簡單的說起

        自定義事件

        let corp = {};   // 自定義一個(gè)公司對象// 這里放一個(gè)列表用來緩存回調(diào)函數(shù)corp.list = [];// 去訂閱事件corp.on = function (fn) {    // 二話不說,直接把fn先存到列表中    this.list.push(fn);};// 發(fā)布事件corp.emit = function () {    // 當(dāng)發(fā)布的時(shí)候再把列表里存的函數(shù)依次執(zhí)行    this.list.forEach(cb => {        cb.apply(this, arguments);    });};// 測試用例corp.on(function (position, salary) {    console.log('你的職位是:' + position);    console.log('期望薪水:' + salary);});corp.on(function(skill, hobby) {    console.log('你的技能有:' + skill);    console.log('愛好:' + hobby);});
        corp.emit('前端', 10000);corp.emit('端茶和倒水', '足球');/* 你的職位是:前端 期望薪水:10000 你的技能有:前端 愛好:10000 你的職位是:端茶和倒水 期望薪水:足球 你的技能有:端茶和倒水 愛好:足球*/?

        上面通過自定義事件實(shí)現(xiàn)了一個(gè)簡單的發(fā)布訂閱模式,不過從打印出來的結(jié)果來看,有點(diǎn)小尷尬。Why?

        因?yàn)樵谡5那闆r下,希望打印的是醬紫的:

        /*    你的職位是:前端    期望薪水:10000        你的技能有:端茶和倒水    愛好:足球*/

        之所以出現(xiàn)此種情況,那是在on方法的時(shí)候一股腦的都將fn函數(shù)全部放到了列表中。然而需要的只是一個(gè)簡單的key值,就可以解決了。讓我們改寫一下上面的代碼

        let corp = {};// 這次換成一個(gè)對象類型的緩存列表corp.list = {};
        corp.on = function(key, fn) { // 如果對象中沒有對應(yīng)的key值 // 也就是說明沒有訂閱過 // 那就給key創(chuàng)建個(gè)緩存列表 if (!this.list[key]) { this.list[key] = []; } // 把函數(shù)添加到對應(yīng)key的緩存列表里 this.list[key].push(fn);};corp.emit = function() { // 第一個(gè)參數(shù)是對應(yīng)的key值 // 直接用數(shù)組的shift方法取出 let key = [].shift.call(arguments), fns = this.list[key]; // 如果緩存列表里沒有函數(shù)就返回false if (!fns || fns.length === 0) { return false; } // 遍歷key值對應(yīng)的緩存列表 // 依次執(zhí)行函數(shù)的方法 fns.forEach(fn => { fn.apply(this, arguments); });};
        // 測試用例corp.on('join', (position, salary) => { console.log('你的職位是:' + position); console.log('期望薪水:' + salary);});corp.on('other', (skill, hobby) => { console.log('你的技能有:' + skill); console.log('愛好:' + hobby);});
        corp.emit('join', '前端', 10000);corp.emit('join', '后端', 10000);corp.emit('other', '端茶和倒水', '足球');/* 你的職位是:前端 期望薪水:10000 你的職位是:后端 期望薪水:10000 你的技能有:端茶和倒水 愛好:足球*/

        來個(gè)通用的

        現(xiàn)在來搞個(gè)通用的發(fā)布訂閱模式實(shí)現(xiàn),和剛才的差不多,不過這次起名也要隆重些了,直接叫event吧,看代碼

        let event = {    list: {},    on(key, fn) {        if (!this.list[key]) {            this.list[key] = [];        }        this.list[key].push(fn);    },    emit() {        let key = [].shift.call(arguments),            fns = this.list[key];
        if (!fns || fns.length === 0) { return false; } fns.forEach(fn => { fn.apply(this, arguments); }); }, remove(key, fn) { // 這回我們加入了取消訂閱的方法 let fns = this.list[key]; // 如果緩存列表中沒有函數(shù),返回false if (!fns) return false; // 如果沒有傳對應(yīng)函數(shù)的話 // 就會(huì)將key值對應(yīng)緩存列表中的函數(shù)都清空掉 if (!fn) { fns && (fns.length = 0); } else { // 遍歷緩存列表,看看傳入的fn與哪個(gè)函數(shù)相同 // 如果相同就直接從緩存列表中刪掉即可 fns.forEach((cb, i) => { if (cb === fn) { fns.splice(i, 1); } }); } }};
        function cat() { console.log('一起喵喵喵');}function dog() { console.log('一起旺旺旺');}
        event.on('pet', data => { console.log('接收數(shù)據(jù)'); console.log(data);});event.on('pet', cat);event.on('pet', dog);// 取消dog方法的訂閱event.remove('pet', dog);// 發(fā)布event.emit('pet', ['二哈', '波斯貓']);/* 接收數(shù)據(jù) [ '二哈', '波斯貓' ] 一起喵喵喵*/

        這樣其實(shí)就實(shí)現(xiàn)了一個(gè)可以使用的發(fā)布訂閱模式了,其實(shí)說起來也是比較簡單的,來一起屢屢思路吧

        思路

        • 創(chuàng)建一個(gè)對象(緩存列表)

        • on方法用來把回調(diào)函數(shù)fn都加到緩存列表中

        • emit方法取到arguments里第一個(gè)當(dāng)做key,根據(jù)key值去執(zhí)行對應(yīng)緩存列表中的函數(shù)

        • remove方法可以根據(jù)key值取消訂閱

        工作中的應(yīng)用

        插廣告

        先給大家看一個(gè)鏈接,在這個(gè)新聞轉(zhuǎn)碼頁的項(xiàng)目中,我負(fù)責(zé)寫下面推薦流的內(nèi)容(就是喜歡的人還看了那里)。如下圖所示

        圈起來的廣告部分,這里并不是我來負(fù)責(zé)的,需要另外一個(gè)負(fù)責(zé)對接廣告業(yè)務(wù)的大牛來實(shí)現(xiàn)的。那么,他想要在我的推薦流中插入廣告應(yīng)該如何實(shí)現(xiàn)呢?

        畢竟不能把我的代碼給他,讓他再拿去開發(fā)吧,這還不夠費(fèi)勁的呢,又要熟悉代碼又要開始寫廣告插入的邏輯,很折騰的,時(shí)間不該這樣的浪費(fèi)掉

        于是就用到了發(fā)布訂閱模式了,我這邊不需要關(guān)注廣告插入的邏輯。我還是我,是顏色不一樣的煙火,哈哈哈,扯遠(yuǎn)了

        溝通后,我只需要把用戶瀏覽到哪一頁的page頁碼傳給他即可。所以我只需要在我開發(fā)的代碼中寫一句話,利用上面實(shí)現(xiàn)的event來表示一下

        // 省略....render() {    // 我只在渲染的時(shí)候    // 把約定好的key和他需要的page頁碼傳過去就可以了    event.emit('soAd', page);}// 省略...

        打點(diǎn)

        再來看一個(gè)鏈接,朋友。打點(diǎn)的用途主要是記錄用戶行為,所以在移動(dòng)圖搜新版開發(fā)的時(shí)候也會(huì)加入打點(diǎn)的代碼,然后統(tǒng)計(jì)一下pv,uv,ctr等數(shù)據(jù),那么直接看圖說話

        如圖所示,當(dāng)用戶向上滑動(dòng)的時(shí)候,會(huì)展示如下的內(nèi)容(這才是我要講的地方)

        這里圈中的“猜你喜歡”部分,也是通過發(fā)請求取到數(shù)據(jù)后渲染的。然而我要做的是給“猜你喜歡”加一個(gè)展現(xiàn)的打點(diǎn)。關(guān)鍵的問題就是時(shí)機(jī),我應(yīng)該什么時(shí)候加打點(diǎn)呢?

        很簡單,我在請求完成并渲染到頁面上的時(shí)候加這個(gè)打點(diǎn)就可以了,來看一下簡單的代碼(這不是項(xiàng)目代碼,只是舉個(gè)栗子)

        // main.jsrender() {    // 省略...    // 當(dāng)渲染到頁面的時(shí)候,發(fā)送這個(gè)打點(diǎn)事件    // 然后另外的一個(gè)專門負(fù)責(zé)打點(diǎn)的模塊里去監(jiān)聽    event.emit('relatedDD', 'related');}
        // log.jsevent.on('relatedDD', type => { console.log(type); // 'related' // monitor是個(gè)打點(diǎn)工具,由超級大牛開發(fā) monitor.log({ type }, 'disp');});

        上面代碼只是簡單的舉栗,如果還有對打點(diǎn)不了解的,那我就稍微簡單的描述一下

        打點(diǎn)常用的就是發(fā)送一個(gè)圖片的請求,根據(jù)請求的次數(shù)來統(tǒng)計(jì)數(shù)據(jù),中間會(huì)根據(jù)不同的參數(shù)去做統(tǒng)計(jì)時(shí)的區(qū)分。

        如:想知道一共有多少用戶看了“猜你喜歡”的內(nèi)容,在篩選數(shù)據(jù)的時(shí)候,會(huì)直接寫上type為related

        所謂栗子就舉到這里吧,舉太多,胳膊會(huì)酸的。哈哈

        不過這并不是結(jié)束,因?yàn)槲野l(fā)現(xiàn)node中的一個(gè)核心模塊(events)正是上面講到的發(fā)布訂閱模式,這不是巧合,也不是演習(xí)。于是春心蕩漾了,手舞足蹈了。跟著api,那就一起來實(shí)現(xiàn)一個(gè),提高一下技藝吧,Let's Go!

        講真-這可是node的核心模塊

        用過node的朋友們,應(yīng)該對這個(gè)模塊不陌生,可以說這個(gè)在node中真的是很重要的模塊了,在使用后發(fā)現(xiàn),這完全是個(gè)大寫的發(fā)布訂閱模式啊

        簡直是無所不在的存在啊,那么廢話不再,實(shí)現(xiàn)依舊。先來看看如何使用吧,來個(gè)測試用例看看

        測試用例

        / {'失戀',  [findboy, drink]}// 監(jiān)聽的目的 就是為了構(gòu)造這樣一個(gè)對象 一對多的關(guān)系    on
        // 發(fā)布的時(shí)候 會(huì)讓數(shù)組的函數(shù)依次執(zhí)行 emit// [findboy, drink]
        // let EventEmitter = require('events');// 這里用接下來我們寫的let EventEmitter = require('./events');let util = require('util');
        function Girl() {}// Girl繼承EventEmitter上的方法util.inherits(Girl, EventEmitter); // 相當(dāng)于Girl.prototype.__proto__ = EventEmitter.prototypelet girl = new Girl();let drink = function (data) { console.log(data); console.log('喝酒');};let findboy = function () { console.log('交友');};
        girl.on('newListener', function (eventName) { // console.log('名稱:' + eventName);});girl.on('結(jié)婚', function() {});girl.setMaxListeners(3);console.log(girl.getMaxListeners());girl.once('失戀', drink); // {'失戀': [drink]}girl.once('失戀', drink); // {'失戀': [drink]}girl.prependListener('失戀', function () { console.log('before');});girl.once('失戀', drink); // {'失戀': [drink]}girl.emit('失戀',?'1');

        以上代碼就是events核心模塊的使用方法,不用吝嗇,快快動(dòng)手敲起來吧

        實(shí)現(xiàn)一個(gè)EventEmitter

        下面來到了最重要也是最激動(dòng)人心的時(shí)刻了,來開始實(shí)現(xiàn)一個(gè)EventEmitter吧

        function EventEmitter() {    // 用Object.create(null)代替空對象{}    // 好處是無雜質(zhì),不繼承原型鏈的東東    this._events = Object.create(null);}// 默認(rèn)最多的綁定次數(shù)EventEmitter.defaultMaxListeners = 10;// 同on方法EventEmitter.prototype.addListener = EventEmitter.prototype.on;// 返回監(jiān)聽的事件名EventEmitter.prototype.eventNames = function () {    return Object.keys(this._events);};// 設(shè)置最大監(jiān)聽數(shù)EventEmitter.prototype.setMaxListeners = function (n) {    this._count = n;};// 返回監(jiān)聽數(shù)EventEmitter.prototype.getMaxListeners = function () {    return this._count ? this._count : this.defaultMaxListeners;};// 監(jiān)聽EventEmitter.prototype.on = function (type, cb, flag) {    // 默認(rèn)值,如果沒有_events的話,就給它創(chuàng)建一個(gè)    if (!this._events) {        this._events = Object.create(null);    }    // 不是newListener 就應(yīng)該讓newListener執(zhí)行以下    if (type !== 'newListener') {        this._events['newListener'] && this._events['newListener'].forEach(listener => {            listener(type);        });    }    if (this._events[type]) {        // 根據(jù)傳入的flag來決定是向前還是向后添加        if (flag) {            this._events[type].unshift(cb);        } else {            this._events[type].push(cb);        }    } else {        this._events[type] = [cb];    }    // 監(jiān)聽的事件不能超過了設(shè)置的最大監(jiān)聽數(shù)    if (this._events[type].length === this.getMaxListeners()) {        console.warn('警告-警告-警告');    }};// 向前添加EventEmitter.prototype.prependListener = function (type, cb) {    this.on(type, cb, true);};EventEmitter.prototype.prependOnceListener = function (type, cb) {    this.once(type, cb, true);};// 監(jiān)聽一次EventEmitter.prototype.once = function (type, cb, flag) {    // 先綁定,調(diào)用后刪除    function wrap() {        cb(...arguments);        this.removeListener(type, wrap);    }    // 自定義屬性    wrap.listen = cb;    this.on(type, wrap, flag);};// 刪除監(jiān)聽類型EventEmitter.prototype.removeListener = function (type, cb) {    if (this._events[type]) {        this._events[type] = this._events[type].filter(listener => {            return cb !== listener && cb !== listener.listen;        });    }};EventEmitter.prototype.removeAllListener = function () {    this._events = Object.create(null);};// 返回所有的監(jiān)聽類型EventEmitter.prototype.listeners = function (type) {    return this._events[type];};// 發(fā)布EventEmitter.prototype.emit = function (type, ...args) {    if (this._events[type]) {        this._events[type].forEach(listener => {            listener.call(this, ...args);        });    }};
        module.exports?=?EventEmitter;

        上面我們通過努力實(shí)現(xiàn)了node的核心模塊events,完成了EventEmitter的功能,可喜可賀,可喜可賀,給自己點(diǎn)個(gè)贊吧!

        完成是完成了,但是大家還是要反復(fù)寫反復(fù)推敲的,畢竟都沒有過目不忘的本領(lǐng),還是要努力的,加油,加油

        哈哈,那么最后的最后,來寫個(gè)小小的總結(jié)

        總結(jié)

        優(yōu)點(diǎn)

        • 對象之間的解耦

        • 異步編程中,可以更松耦合的代碼編寫

        缺點(diǎn)

        • 創(chuàng)建訂閱者本身要消耗一定的時(shí)間和內(nèi)存

        • 多個(gè)發(fā)布者和訂閱者嵌套一起的時(shí)候,程序難以跟蹤維護(hù)

        強(qiáng)如發(fā)布訂閱模式,也是勁酒雖好,不要貪杯的道理哦。過度使用的話,都會(huì)出現(xiàn)上述缺點(diǎn)的問題。不過合理開發(fā)合理利用,這都不是什么大問題的。

        好好利用這個(gè)最常見的模式吧,給你的編程帶來不小的升華!今天就寫到這里吧,感謝觀看了。哈哈,有緣下次再見!See U Again

        瀏覽 54
        點(diǎn)贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

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

        手機(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>
            久久天天操 | 欧美性爱亚洲激情 | 女人荫蒂被添的好舒服视频 | 一级a一级a爰片免费免软件下载 | 美女奶子无遮挡 | 女人高潮网站 | 肏逼网战| 四川女人高潮毛片 | 天美麻花星空果冻传媒的背景故事 | 色综合色狠狠天天综合色 |