JavaScript 发布—订阅者模式 ★

目录

1、啥是发布订阅者模式

2、jQuery 中的发布订阅

3、基于ES6封装发布订阅

4、数组塌陷问题


1、啥是发布订阅者模式

发布订阅者模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。在 JavaScript 开发中,一般用事件模型来代替传统的发布—订阅者模式。

在实际开发中,只要我们在 DOM 节点上绑定过事件函数,那就曾经使用过发布-订阅者模式,下面看个例子:

document.body.addEventListener('click',function(){
    alert('你点击了页面');
});
document.body.click();    //模仿用户点击

这个例子中监控了用户点击 document.body 的动作,但是我们没有办法预知用户将在什么时候点击。所以我们订阅 document.body 上的 click 事件,当 body 被点击时,body 节点便会向订阅者发布消息。当然还可以随意的增加或删除订阅者,增加任何订阅者都不会影响发布者代码的编写。

2、jQuery 中的发布订阅

设想一个例子,当用户点击一个按钮时,依次执行绑定在按钮上的事件。在不使用发布订阅者模式的情况下,如果开发过程中有新的方法,则需要不仅需要手动把新的方法添加进去,还需要找到 click() 的位置。

针对上面的问题,jquery 的发布订阅者模式是,创建一个事件池:($.Callbacks()、add/remove、fire)

<button class='submit'>按钮</button>
<script src="../jquery.main.js"></script>
<script>
    let fun1 = function(){
        console.log(1);
    };
    let fun2 = function(){
        console.log(2);
    };
    //=>创建一个事件池$.Callbacks()
    let $pond = $.callbacks();
    $('.submit').click(function(){
        //=>点击的时候通知事件池中的方法执行,而且还可以给每个方法都传递实参
        $pond.fire(100,200);    //可以传参
        
    });
    $.pond.add(fun1);
    $.pond.add(fun2);
    
    let fun3 = function(m,n){    //如果再需要添加方法
        console.log(m+n);        //300
    };
    $pond.add(fun3);    //直接通过add()就可以添加订阅者方法了
</script>

3、基于ES6封装发布订阅

(1)封装:上面这种方法依托 jquery 的库,但是真实项目中有可能基于 Vue、React 等框架,是不会用到 jquery 的,但是有时候还需要用到发布订阅这种思想,这个时候就需要自己封装一个发布订阅库。

class Subscribe{
    constructor(){
        this.pond = [];    //创建一个事件池,用来存储后期需要执行的方法
    }
    add(func){    //向事件池中追加方法
        let flag = this.pond.some(item => {
            return item === func;
        })
        !flag ? this.pond.push(func) : null;
    }
    remove(func){    //从事件池中移除方法
        let pond = this.pond;
        for(let i=0;i<pond.length;i++){
            let item = pond[i];
            if(item === func){    //找到之后将其移除
                //pond.splice(i,1);        //不能这么写,会导致数组塌陷问题
                pond[i] = null;            //所以不能真移除,只能把当前项赋值为null
                break;
            }
        }
    }
    fire(...args){    //通知事件池中的方法,按照顺序依次执行
        let pond = this.pond;
        for(let i=0;i<pond.length;i++){
            let item = pond[i];
            if(typeof item !== 'function')
                pond.splice(i,1);
                i--;
                continue;
            item.call(this,...args);    //this此时指向Subscribe实例
        }
    }
}

(2)测试:上述代码就已经将发布订阅模式封装成了一个插件,保存到一个 .js 文件中,如果使用,直接在项目中引用即可。

<button class='submit'>按钮</button>
<script src="../subscribe.js"></script>
<script>
    let pond = new Subscribe();
    document.querySelector('.submit').onclick = function(event){
        pond.fire(event);    //执行事件池中的方法,并将事件对象传递进来
    }
    let fun1 = function(){
        console.log(1);
    };
    let fun2 = function(){
        console.log(2);
    };
    pond.add(fun1);
    pond.add(fun1);    //验证是否去重
    pond.add(fun2);
    let fun3 = function(event){
        console.log(3,event);
    };
    pond.add(fun3);
</script>

4、数组塌陷问题

我们在封装发布订阅这模式的时候内部的事件池是用数组来代替的,但是在实际需求中可能会有这种情况:当依次执行事件池中的事件的时候,执行 fun2 的时候,它的操作是 remove 事件池中的 fun1 方法,这个时候就会导致数组塌陷问题,具体如下图:

因为要考虑到上面这种数组塌陷的情况,所以在封装发布订阅插件的时候,要移除某个方法就不能直接使用 splice() 方法,而是先将该方法所在的数组位置设置为 null,当执行 fire() 方法的时候,依次执行事件池中的函数,再循环判断数组当前位置上是否为函数,如果不是函数而是 null,再将其去除,然后将数组下标减一,这样就可以防止数组塌陷问题了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值