成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

JavaScript 發(fā)布-訂閱模式

13651657101 / 2995人閱讀

摘要:發(fā)布訂閱模式訂閱者把自己想訂閱的事件注冊到調(diào)度中心,當(dāng)發(fā)布者發(fā)布該事件到調(diào)度中心,也就是該事件觸發(fā)時,由調(diào)度中心統(tǒng)一調(diào)度訂閱者注冊到調(diào)度中心的處理代碼。

發(fā)布-訂閱模式,看似陌生,其實不然。工作中經(jīng)常會用到,例如 Node.js EventEmitter 中的 on 和 emit 方法;Vue 中的 $on$emit 方法。他們都使用了發(fā)布-訂閱模式,讓開發(fā)變得更加高效方便。
一、 什么是發(fā)布-訂閱模式 1. 定義

發(fā)布-訂閱模式其實是一種對象間一對多的依賴關(guān)系,當(dāng)一個對象的狀態(tài)發(fā)送改變時,所有依賴于它的對象都將得到狀態(tài)改變的通知。

訂閱者(Subscriber)把自己想訂閱的事件注冊(Subscribe)到調(diào)度中心(Event Channel),當(dāng)發(fā)布者(Publisher)發(fā)布該事件(Publish Event)到調(diào)度中心,也就是該事件觸發(fā)時,由調(diào)度中心統(tǒng)一調(diào)度(Fire Event)訂閱者注冊到調(diào)度中心的處理代碼。

2. 例子

比如我們很喜歡看某個公眾號號的文章,但是我們不知道什么時候發(fā)布新文章,要不定時的去翻閱;這時候,我們可以關(guān)注該公眾號,當(dāng)有文章推送時,會有消息及時通知我們文章更新了。

上面一個看似簡單的操作,其實是一個典型的發(fā)布訂閱模式,公眾號屬于發(fā)布者,用戶屬于訂閱者;用戶將訂閱公眾號的事件注冊到調(diào)度中心,公眾號作為發(fā)布者,當(dāng)有新文章發(fā)布時,公眾號發(fā)布該事件到調(diào)度中心,調(diào)度中心會及時發(fā)消息告知用戶。

二、 如何實現(xiàn)發(fā)布-訂閱模式? 1. 實現(xiàn)思路

創(chuàng)建一個對象

在該對象上創(chuàng)建一個緩存列表(調(diào)度中心)

on 方法用來把函數(shù) fn 都加到緩存列表中(訂閱者注冊事件到調(diào)度中心)

emit 方法取到 arguments 里第一個當(dāng)做 event,根據(jù) event 值去執(zhí)行對應(yīng)緩存列表中的函數(shù)(發(fā)布者發(fā)布事件到調(diào)度中心,調(diào)度中心處理代碼)

off 方法可以根據(jù) event 值取消訂閱(取消訂閱)

once 方法只監(jiān)聽一次,調(diào)用完畢后刪除緩存函數(shù)(訂閱一次)

2. demo1

我們來看個簡單的 demo,實現(xiàn)了 on 和 emit 方法,代碼中有詳細注釋。

// 公眾號對象
let eventEmitter = {};

// 緩存列表,存放 event 及 fn
eventEmitter.list = {};

// 訂閱
eventEmitter.on = function (event, fn) {
    let _this = this;
    // 如果對象中沒有對應(yīng)的 event 值,也就是說明沒有訂閱過,就給 event 創(chuàng)建個緩存列表
    // 如有對象中有相應(yīng)的 event 值,把 fn 添加到對應(yīng) event 的緩存列表里
    (_this.list[event] || (_this.list[event] = [])).push(fn);
    return _this;
};

// 發(fā)布
eventEmitter.emit = function () {
    let _this = this;
    // 第一個參數(shù)是對應(yīng)的 event 值,直接用數(shù)組的 shift 方法取出
    let event = [].shift.call(arguments),
        fns = _this.list[event];
    // 如果緩存列表里沒有 fn 就返回 false
    if (!fns || fns.length === 0) {
        return false;
    }
    // 遍歷 event 值對應(yīng)的緩存列表,依次執(zhí)行 fn
    fns.forEach(fn => {
        fn.apply(_this, arguments);
    });
    return _this;
};

function user1 (content) {
    console.log("用戶1訂閱了:", content);
};

function user2 (content) {
    console.log("用戶2訂閱了:", content);
};

// 訂閱
eventEmitter.on("article", user1);
eventEmitter.on("article", user2);

// 發(fā)布
eventEmitter.emit("article", "Javascript 發(fā)布-訂閱模式");

/*
    用戶1訂閱了: Javascript 發(fā)布-訂閱模式
    用戶2訂閱了: Javascript 發(fā)布-訂閱模式
*/
3. demo2

這一版中我們補充了一下 once 和 off 方法。

let eventEmitter = {
    // 緩存列表
    list: {},
    // 訂閱
    on (event, fn) {
        let _this = this;
        // 如果對象中沒有對應(yīng)的 event 值,也就是說明沒有訂閱過,就給 event 創(chuàng)建個緩存列表
        // 如有對象中有相應(yīng)的 event 值,把 fn 添加到對應(yīng) event 的緩存列表里
        (_this.list[event] || (_this.list[event] = [])).push(fn);
        return _this;
    },
    // 監(jiān)聽一次
    once (event, fn) {
        // 先綁定,調(diào)用后刪除
        let _this = this;
        function on () {
            _this.off(event, on);
            fn.apply(_this, arguments);
        }
        on.fn = fn;
        _this.on(event, on);
        return _this;
    },
    // 取消訂閱
    off (event, fn) {
        let _this = this;
        let fns = _this.list[event];
        // 如果緩存列表中沒有相應(yīng)的 fn,返回false
        if (!fns) return false;
        if (!fn) {
            // 如果沒有傳 fn 的話,就會將 event 值對應(yīng)緩存列表中的 fn 都清空
            fns && (fns.length = 0);
        } else {
            // 若有 fn,遍歷緩存列表,看看傳入的 fn 與哪個函數(shù)相同,如果相同就直接從緩存列表中刪掉即可
            let cb;
            for (let i = 0, cbLen = fns.length; i < cbLen; i++) {
                cb = fns[i];
                if (cb === fn || cb.fn === fn) {
                    fns.splice(i, 1);
                    break
                }
            }
        }
        return _this;
    },
    // 發(fā)布
    emit () {
        let _this = this;
        // 第一個參數(shù)是對應(yīng)的 event 值,直接用數(shù)組的 shift 方法取出
        let event = [].shift.call(arguments),
            fns = _this.list[event];
        // 如果緩存列表里沒有 fn 就返回 false
        if (!fns || fns.length === 0) {
            return false;
        }
        // 遍歷 event 值對應(yīng)的緩存列表,依次執(zhí)行 fn
        fns.forEach(fn => {
            fn.apply(_this, arguments);
        });
        return _this;
    }
};

function user1 (content) {
    console.log("用戶1訂閱了:", content);
}

function user2 (content) {
    console.log("用戶2訂閱了:", content);
}

function user3 (content) {
    console.log("用戶3訂閱了:", content);
}

function user4 (content) {
    console.log("用戶4訂閱了:", content);
}

// 訂閱
eventEmitter.on("article1", user1);
eventEmitter.on("article1", user2);
eventEmitter.on("article1", user3);

// 取消user2方法的訂閱
eventEmitter.off("article1", user2);

eventEmitter.once("article2", user4)

// 發(fā)布
eventEmitter.emit("article1", "Javascript 發(fā)布-訂閱模式");
eventEmitter.emit("article1", "Javascript 發(fā)布-訂閱模式");
eventEmitter.emit("article2", "Javascript 觀察者模式");
eventEmitter.emit("article2", "Javascript 觀察者模式");

// eventEmitter.on("article1", user3).emit("article1", "test111");

/*
    用戶1訂閱了: Javascript 發(fā)布-訂閱模式
    用戶3訂閱了: Javascript 發(fā)布-訂閱模式
    用戶1訂閱了: Javascript 發(fā)布-訂閱模式
    用戶3訂閱了: Javascript 發(fā)布-訂閱模式
    用戶4訂閱了: Javascript 觀察者模式
*/
三、 Vue 中的實現(xiàn)

有了發(fā)布-訂閱模式的知識后,我們來看下 Vue 中怎么實現(xiàn) $on$emit 的方法,直接看源碼:

function eventsMixin (Vue) {
    var hookRE = /^hook:/;
    Vue.prototype.$on = function (event, fn) {
        var this$1 = this;

        var vm = this;
        // event 為數(shù)組時,循環(huán)執(zhí)行 $on
        if (Array.isArray(event)) {
            for (var i = 0, l = event.length; i < l; i++) {
                this$1.$on(event[i], fn);
            }
        } else {
            (vm._events[event] || (vm._events[event] = [])).push(fn);
            // optimize hook:event cost by using a boolean flag marked at registration 
            // instead of a hash lookup
            if (hookRE.test(event)) {
                vm._hasHookEvent = true;
            }
        }
        return vm
    };

    Vue.prototype.$once = function (event, fn) {
        var vm = this;
        // 先綁定,后刪除
        function on () {
            vm.$off(event, on);
            fn.apply(vm, arguments);
        }
        on.fn = fn;
        vm.$on(event, on);
        return vm
    };

    Vue.prototype.$off = function (event, fn) {
        var this$1 = this;

        var vm = this;
        // all,若沒有傳參數(shù),清空所有訂閱
        if (!arguments.length) {
            vm._events = Object.create(null);
            return vm
        }
        // array of events,events 為數(shù)組時,循環(huán)執(zhí)行 $off
        if (Array.isArray(event)) {
            for (var i = 0, l = event.length; i < l; i++) {
                this$1.$off(event[i], fn);
            }
            return vm
        }
        // specific event
        var cbs = vm._events[event];
        if (!cbs) {
            // 沒有 cbs 直接 return this
            return vm
        }
        if (!fn) {
            // 若沒有 handler,清空 event 對應(yīng)的緩存列表
            vm._events[event] = null;
            return vm
        }
        if (fn) {
            // specific handler,刪除相應(yīng)的 handler
            var cb;
            var i$1 = cbs.length;
            while (i$1--) {
                cb = cbs[i$1];
                if (cb === fn || cb.fn === fn) {
                    cbs.splice(i$1, 1);
                    break
                }
            }
        }
        return vm
    };

    Vue.prototype.$emit = function (event) {
        var vm = this;
        {
            // 傳入的 event 區(qū)分大小寫,若不一致,有提示
            var 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 + ""."
                );
            }
        }
        var cbs = vm._events[event];
        if (cbs) {
            cbs = cbs.length > 1 ? toArray(cbs) : cbs;
            // 只取回調(diào)函數(shù),不取 event
            var args = toArray(arguments, 1);
            for (var i = 0, l = cbs.length; i < l; i++) {
                try {
                    cbs[i].apply(vm, args);
                } catch (e) {
                    handleError(e, vm, ("event handler for "" + event + """));
                }
            }
        }
        return vm
    };
}

/***
   * Convert an Array-like object to a real Array.
   */
function toArray (list, start) {
    start = start || 0;
    var i = list.length - start;
    var ret = new Array(i);
    while (i--) {
          ret[i] = list[i + start];
    }
    return ret
}

實現(xiàn)思路大體相同,如上第二點中的第一條:實現(xiàn)思路。Vue 中實現(xiàn)的方法支持訂閱數(shù)組事件。

四、 總結(jié) 1. 優(yōu)點

對象之間解耦

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

2. 缺點

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

雖然可以弱化對象之間的聯(lián)系,多個發(fā)布者和訂閱者嵌套一起的時候,程序難以跟蹤維護

五、 擴展(發(fā)布-訂閱模式與觀察者模式的區(qū)別)

很多地方都說發(fā)布-訂閱模式是觀察者模式的別名,但是他們真的一樣嗎?是不一樣的。

直接上圖:

觀察者模式:觀察者(Observer)直接訂閱(Subscribe)主題(Subject),而當(dāng)主題被激活的時候,會觸發(fā)(Fire Event)觀察者里的事件。

發(fā)布訂閱模式:訂閱者(Subscriber)把自己想訂閱的事件注冊(Subscribe)到調(diào)度中心(Event Channel),當(dāng)發(fā)布者(Publisher)發(fā)布該事件(Publish Event)到調(diào)度中心,也就是該事件觸發(fā)時,由調(diào)度中心統(tǒng)一調(diào)度(Fire Event)訂閱者注冊到調(diào)度中心的處理代碼。

差異

在觀察者模式中,觀察者是知道 Subject 的,Subject 一直保持對觀察者進行記錄。然而,在發(fā)布訂閱模式中,發(fā)布者和訂閱者不知道對方的存在。它們只有通過消息代理進行通信。

在發(fā)布訂閱模式中,組件是松散耦合的,正好和觀察者模式相反。

觀察者模式大多數(shù)時候是同步的,比如當(dāng)事件觸發(fā),Subject 就會去調(diào)用觀察者的方法。而發(fā)布-訂閱模式大多數(shù)時候是異步的(使用消息隊列)。

觀察者模式需要在單個應(yīng)用程序地址空間中實現(xiàn),而發(fā)布-訂閱更像交叉應(yīng)用模式。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/109747.html

相關(guān)文章

  • JavaScript設(shè)計模式發(fā)布-訂閱模式(觀察者模式)-Part1

    摘要:設(shè)計模式與開發(fā)實踐讀書筆記。發(fā)布訂閱模式又叫觀察者模式,它定義了對象之間的一種一對多的依賴關(guān)系。附設(shè)計模式之發(fā)布訂閱模式觀察者模式數(shù)據(jù)結(jié)構(gòu)和算法系列棧隊列優(yōu)先隊列循環(huán)隊列設(shè)計模式系列設(shè)計模式之策略模式 《JavaScript設(shè)計模式與開發(fā)實踐》讀書筆記。 發(fā)布-訂閱模式又叫觀察者模式,它定義了對象之間的一種一對多的依賴關(guān)系。當(dāng)一個對象的狀態(tài)發(fā)生改變時,所有依賴它的對象都將得到通知。 例...

    muzhuyu 評論0 收藏0
  • JavaScript設(shè)計模式發(fā)布-訂閱模式(觀察者模式)-Part2

    摘要:設(shè)計模式與開發(fā)實踐讀書筆記??创宋恼虑?,建議先看設(shè)計模式之發(fā)布訂閱模式觀察者模式在中,已經(jīng)介紹了什么是發(fā)布訂閱模式,同時,也實現(xiàn)了發(fā)布訂閱模式。 《JavaScript設(shè)計模式與開發(fā)實踐》讀書筆記。 看此文章前,建議先看JavaScript設(shè)計模式之發(fā)布-訂閱模式(觀察者模式)-Part1 在Part1中,已經(jīng)介紹了什么是發(fā)布-訂閱模式,同時,也實現(xiàn)了發(fā)布-訂閱模式。但是,就Part1...

    Charlie_Jade 評論0 收藏0
  • JavaScript設(shè)計模式發(fā)布-訂閱模式(觀察者模式)-Part2

    摘要:設(shè)計模式與開發(fā)實踐讀書筆記??创宋恼虑?,建議先看設(shè)計模式之發(fā)布訂閱模式觀察者模式在中,已經(jīng)介紹了什么是發(fā)布訂閱模式,同時,也實現(xiàn)了發(fā)布訂閱模式。 《JavaScript設(shè)計模式與開發(fā)實踐》讀書筆記。 看此文章前,建議先看JavaScript設(shè)計模式之發(fā)布-訂閱模式(觀察者模式)-Part1 在Part1中,已經(jīng)介紹了什么是發(fā)布-訂閱模式,同時,也實現(xiàn)了發(fā)布-訂閱模式。但是,就Part1...

    chemzqm 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<