美文网首页
观察者模式-网站登录

观察者模式-网站登录

作者: RedLee666 | 来源:发表于2019-11-17 18:51 被阅读0次

        观察者模式也叫发布-订阅者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。最熟悉的使用观察者模式的地方莫过于是在DOM节点上绑定事件函数。

    添加事件并不会影响到发布者代码的编写
    document.body.addEventListener('click', function() {
      alert(1);
    }, false);
    
    document.body.addEventListener('click', function() {
      alert(2);
    }, false);
    
    document.body.addEventListener('click', function() {
      alert(3);
    }, false);
    
    document.body.click(); //1, 2, 3
    

    定义一个发布-订阅功能的对象:

    let event = {
        clientList: {},
        listen: function(key, fn) {
          if(!this.clientList[key]){
            this.clientList[key] = [];
          }
          this.clientList[key].push(fn);//订阅的消息添加进缓存列表
        },
        trigger: function() {
            let key = Array.prototype.shift.call(arguments), fns =this.clientList[key];
            if(!fns || fns.length == 0) {//如果没有绑定对应的消息
                return false;
            }
            for(let i = 0, fn; fn = fns[i++]; ) {
                fn.apply(this, arguments);
            }
        },
        remove: function(key, fn) {
            let fns = this.clientList[key];
            if(!fns) {//如果key对应的消息没有被人订阅,则直接返回
                return false;
            }
            if(!fns) {//如果没有传入具体的回调函数,表示需要取消key对应消息的所有订阅
                fns && (fns.length = 0);
            } else {
                for(let l =  fns.length - 1; l >= 0; l--) {//反向便利订阅的回调函数列表
                    let _fn = fns[l];
                    if(_fn === fn) {
                        fns.splice(l, 1);//删除订阅者的回调函数
                    }
                }
            }
        }
    }
    

        模拟场景1:订阅某一户型的房子的价格信息

    let salesOffices = {};
    let installEvent = function(obj) {
        for (let i in event) {
            obj[i] = event[i];
        }
    }
    installEvent(salesOffices);
    
    //小明说我想要88平米的房子,有房源后通知我
    salesOffices.listen('squareMeter88', fn1 = function(price){
        console.log('价格(小明)=' + price);
    })
    
    //小红说我也想要88平米的房子,有房源后通知我
    salesOffices.listen('squareMeter88', fn2 = function(price) {
        console.log('价格(小红)=' + price);
    })
    
    //有88平米的房源了,售价两百万,通知各个订阅的用户
    salesOffices.trigger('squareMeter88', 2000000);
    //价格(小明)=2000000
    //价格(小红)=2000000
    
    //小明说他不需要订阅88平米的房源了
    salesOffices.remove('squareMeter88', fn1);
    
    salesOffices.trigger('squareMeter88', 1000000);
    //价格(小红)=2000000
    

        模拟场景2:页面上需要显示用户信息,但是我们不知道除了页面header头部、nav导航、消息列表、购物车之外,将来还有哪些模块需要使用这些用户信息。如果它们和用户信息模块产生了强耦合,比如下面这样的形式:

    login.succ为登录成功回调
    login.succ(function(data) {
        header.setAtar(data.avtar);//设置header模块的头像
        nav.setAtar(data.avatar);//设置导航模块的头像
        messge.refrest();//刷新消息列表
        cart.refresh();//刷新购物车列表
        ...
    })
    

        后来需要登录成功后刷新地址列表,就需要在这里添加代码。我们会越来越疲于应付这些突如其来的业务要求。用发布=订阅者模式重写之后,对用户信息感兴趣的业务模块将自行订阅登录成功后的消息事件。当登录成功时,登录模块只需要发布登录成功的消息,而业务模块接收到消息后,就会开始进行各自的业务处理,登录模块并不关心业务方究竟要做什么,也不想去了解他们的内部细节。

    $ajax('http//xxx.com?login', function(data) {
      login.trigger('loginSucc', data);
    });
    

        各模块监听登录成功的消息:

    let header = (function() {
        login.listen('loginSucc', function(data) {
            header.setAvatar(data.avtar);
        });
        return {
            setAvatar: function(data) {
                console.log('设置header模块的头像');
            }
        }
    })();
    

        全局模式:上面简略实现了一下观察者模式,除此之外还可以创建一个全局对象,降低订阅者对发布者的耦合性,即订阅者不需要知道消息来自哪个发布者,发布者也不知道消息会推送给哪个发布者。即对发布-订阅者模式的封装。
        缓存消息:除了可以对发布-订阅者再次封装,还可以设置为不用先订阅再发布,即将发布的内容缓存起来,后面的订阅者也可以接收到此前的消息。此时需要建立一个存放离线事件的堆栈,当事件发布的时候,如果此时还没有订阅者来订阅这个事件,我们暂时把发布事件的动作包裹在一个函数里,这些包装函数将被存入堆栈中,等到终于有对象来订阅此事件的的时候,我们将遍历堆栈并且依次执行这些包装函数,也就是重新发布里面的事件。当然离线事件的生命周期只有一次,就像QQ的未读消息只会被重新阅读一次,所以刚才的操作我们只能进行一次。

    相关文章

      网友评论

          本文标题:观察者模式-网站登录

          本文链接:https://www.haomeiwen.com/subject/boipictx.html