实现发布订阅者模式

311 阅读2分钟

基于单例实现发布订阅者模式

先定义存储事件的数据结构,我们选取一下的这种数据结构。

{
    'click': [fn1, fn2, fn3],
    'change': [fn1, fn2, fn3]
}

使用方式,以及效果

const fn1 = function(data) {
    console.log(data)
}

const fn2 = function(data) {
    console.log(data)
}

我们希望$ 触发 'xxx' 事件的时候调用 fn1 这个函数。并且把参数传递 fn1
$.on('xxx', fn1)       

触发已经注册好的 'xxx' 事件函数的调用
$.emit('xxx', {name: '小明'})

我们希望移除 'xxx' 事件的 fn1 注册的事件回调函数 。
$.off('xxx', fn1)
var subscribe = (function () {
    // 定义事件池
    let pond = {};
    // 事件池中方法的管理, 把函数按照事件类型加入到池子中去。
    const on = function (type, fn) {
        // 先判断有没有对应的事件类型,没有的话默认给空数组
        !pond.hasOwnProperty(type) ? (pond[type] = []) : null;
        let arr = pond[type];
        // 验证添添加对应的类型有没有重复的,如果有重复的就不执行添加操作
        for (let i = 0; i < arr.length; i++) {
            if (arr[i] === fn) {
                return;
            }
        }
        // 等价的
        //   arr.includes(fn) return
        arr.push(fn);
    };
    // 触发事件池中对应的方法
    const emit = function (type, ...params) {
        // 依次执行对应类型的事件函数中的方法,并把参数传进去
        let arr = pond[type] || [];
        // 注意点
        // 方法1、为了防止在触发方法的过程中删除事件池中的方法,所以克隆一份来执行
        arr = arr.slice(0)
        for (let i = 0; i < arr.length; i++) {
            arr[i](params);
        }
    };
    // 取消事件
    const off = function (type, fn) {
        let arr = pond[type] || [];
        for (let i = 0; i < arr.length; i++) {
            if (arr[i] === fn) {
                // 从中移除
                arr.splice(i, 1);
            }
        }
    };
    return {
        on,
        emit,
        off,
    };
})();

测试代码

<style>
    #box {
        width: 100px;
        height: 100px;
        background: pink;
    }
</style>    
<div id="box"></div>


const fn1 = function (params) {
    console.log(1, params);
};
const fn2 = function (params) {
    console.log(2, params);
    subscribe.off("A", fn1);
    subscribe.off("A", fn2);
};
const fn3 = function (params) {
    console.log(3, params);
};
const fn4 = function (params) {
    console.log(4, params);
};
const fn5 = function (params) {
    console.log(5, params);
};

subscribe.on("A", fn1);
subscribe.on("A", fn2);
subscribe.on("A", fn3);
subscribe.on("A", fn4);
subscribe.on("A", fn5);

let box = document.querySelector("#box");
box.onclick = function () {
    subscribe.emit("A", 1, 2);
};

数组塌陷问题

var subscribe = (function () {
    // 定义事件池
    // {
    //     xxx: [function(){}, function(){}]
    // }
    let pond = {};
    // 事件池中方法的管理, 把函数按照事件类型加入到池子中去。
    const on = function (type, fn) {
        // 先判断有没有对应的事件类型,没有的话默认给空数组
        !pond.hasOwnProperty(type) ? (pond[type] = []) : null;
        let arr = pond[type];
        // 验证添添加对应的类型有没有重复的,如果有重复的就不执行添加操作
        for (let i = 0; i < arr.length; i++) {
            if (arr[i] === fn) {
                return;
            }
        }
        // 等价的
        //   arr.includes(fn) return
        arr.push(fn);
    };
    // 触发事件池中对应的方法
    const emit = function (type, ...params) {
        console.log('hihi')
        // 依次执行对应类型的事件函数中的方法,并把参数传进去
        let arr = pond[type] || [];
        for (let i = 0; i < arr.length; i++) {
            // 是函数的话再执行
            if (typeof arr[i] === "function") {
                arr[i](params);
            } else {
                // 不是函数的话就删除当前位置的元素, 从当前位置开始删除一个元素 
                arr.splice(i, 1);
                // 并且让遍历的下标不往后移动
                i--
            }
        }
        console.log(arr)
    };
    // 取消事件
    const off = function (type, fn) {
        let arr = pond[type] || [];
        for (let i = 0; i < arr.length; i++) {
            if (arr[i] === fn) {
                // 不改变原始数组长度
                arr[i] = null;
            }
        }
    };
    return {
        on,
        emit,
        off,
    };
})();

image.png