发布订阅设计模式

106 阅读1分钟

发布订阅设计模式(对观察者模式的一个升级)

灵感来源于DOM2级事件的事件池机制

(function () {
    // 基于工厂设计模式,实现把Sub当作普通函数执行,但是也可以创造其实例
    function Sub() {
        return new init();
    }

    function init() {
        // 每一次创建实例都挂在一个私有的事件池(自定义事件池)
        this.listeners = {};
    }

    // 原型链的处理
    Sub.prototype = {
        constructor: Sub,
        // 向指定自定义事件的容器中追加方法
        on(type, func) {
            // 首先验证一下是否存在这个自定义事件,不存在创建一个
            !this.listeners.hasOwnProperty(type) ? this.listeners[type] = [] : null;
            let arr = this.listeners[type];
            if (!arr.includes(func)) {
                arr.push(func);
            }
        },
        // 从指定自定义事件的容器中移除方法
        off(type, func) {
            let arr = this.listeners[type];
            if (!arr) return;
            for (let i = 0; i < arr.length; i++) {
                if (arr[i] === func) {
                    /* // splice删除是改变原始数组的:删除项后面的每一项索引都要向前提一位
                    // “数组塌陷”,这样的操作很容易带来问题
                    arr.splice(i, 1); */

                    // 为了防止塌陷导致的问题,我们在移除的时候,不要修改原始数组的结构 
                    arr[i] = null;
                    break;
                }
            }
        },
        // 通知指定自定义事件的容器中的方法执行
        fire(type, ...args) {
            let arr = this.listeners[type];
            if (!arr) return;
            for (let i = 0; i < arr.length; i++) {
                let item = arr[i];
                if (typeof item === "function") {
                    item.call(this, ...args);
                } else {
                    // 也会“数组塌陷”,我们下一轮循环还是从当前的索引开始即可
                    arr.splice(i, 1);
                    i--;
                }
            }
        }
    };
    init.prototype = Sub.prototype;

    window.Sub = Sub;
})();