观察者模式-网站登录

2019-11-17  本文已影响0人  RedLee666

    观察者模式也叫发布-订阅者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。最熟悉的使用观察者模式的地方莫过于是在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的未读消息只会被重新阅读一次,所以刚才的操作我们只能进行一次。

上一篇下一篇

猜你喜欢

热点阅读