观察者模式(发布订阅模式)

Q:你封装过发布订阅者模式吗

今天被小伙伴提问了这么一个问题,于是抽出时间研究了一下

定义

一个被观察者所管理所有相依于它的观察者物件,并且在本身的状态改变时主动发出通知

理解

  • 我是这么理解的,就像面试,面试官说回去等通知,面试者能不能做出响应则要依赖于面试官是否发出通知

  • 此时的角色我们可以这么看:

    • 面试官:被观察者
    • 面试者:观察者
  • 当然我们也可以理解为很多人订阅了这个接口,但是接口并不会触发,只有在发布的时候才会触发。

  • 此时的角色是

    • 接口:被观察者
    • 人:观察者

在js中,事件就是发布订阅模式

  • 我们给一个元素添加点击操作,添加完就相当于是订阅,但是并不会触发。只有当点击之后才会触发,这个点击的过程就是发布

  • 因为可以给同一元素添加很多事件,所以订阅者可以有多个,但是发布只有一个,调用后所有的订阅者都会触发

  • 打个比方,我们给一个元素写三个onclick,添加完以后都没有反应,但是一旦点击,三个onclick都会触发

举个栗子

function Foo(){
}
//类似于jq中添加事件的三个元素:元素(obj)、事件(evevts)、回调(fn)
Foo.prototype.bind = function(obj,events,fn){  
//往对象身上挂载自定义属性
//这样就可以根据对象obj找到相应的事件从而找到相应的回调函数
    obj.listeners = obj.listeners || {};
    obj.listeners[events] = obj.listeners[events] || [];
    obj.listeners[events].push(fn);
};
//触发自定义事件,相当于jq中的trigger()
Foo.prototype.fire = function(obj,events){
//循环找到当前对象下面指定事件下面的所有回调函数,对其进行调用
    for(var i=0;i<obj.listeners[events].length;i++){
        obj.listeners[events][i]();
    }
};
var div1 = document.getElementById('div1');
var obj = new Foo();

//给div添加两个自定义事件=>观察者(订阅)
obj.bind(div1,'show',function(){
    console.log(123);
});
obj.bind(div1,'show',function(){
    console.log(456);
});
//fire触发的时候,上面两个事件都会触发=>被观察者(发布)
obj.fire(div1,'show');

再举个例子

拖拽发布,滚动发布

function Foo(){
}
Foo.prototype.bind = function(obj,events,fn){
    obj.listeners = obj.listeners || {};
    obj.listeners[events] = obj.listeners[events] || [];
    obj.listeners[events].push(fn);
};
Foo.prototype.fire = function(obj,events){
    var args = Array.prototype.slice.call(arguments).slice(2);
    for(var i=0;i<obj.listeners[events].length;i++){
        obj.listeners[events][i].apply(obj,args);
    }
};

var div1 = document.getElementById('div1');
function ScaleMethod(elem){
    this.elem = elem;
    this.scale();
    this.wheel();
}
ScaleMethod.prototype.scale = function(){
    var downX = 0;
    var downY = 0;
    var downW = 0;
    var downH = 0;
    var This = this;
    this.elem.onmousedown = function(ev){
        downX = ev.pageX;
        downY = ev.pageY;
        downW = This.elem.offsetWidth;
        downH = This.elem.offsetHeight;
        document.onmousemove = function(ev){
            This.elem.style.width = ev.pageX - downX + downW + 'px';
            This.elem.style.height = ev.pageY - downY + downH + 'px';
            obj.fire(This,'scale',This.elem.offsetWidth,This.elem.offsetHeight);
        };
        document.onmouseup = function(){
            document.onmousemove = null;
            document.onmouseup = null;
        };
        return false;
    };
};
ScaleMethod.prototype.wheel = function(){
    var This = this;
    this.elem.onmousewheel = function(ev){
        if(ev.wheelDelta > 0){
            this.style.height = this.offsetHeight - 10 + 'px';
        }
        else{
            this.style.height = this.offsetHeight + 10 + 'px';
        }
        obj.fire(This,'scale',This.elem.offsetWidth,This.elem.offsetHeight);
    };
};
var obj = new Foo();
var s1 = new ScaleMethod(div1);
//订阅者,添加显示出滚轮滚动时的当前坐标
obj.bind(s1,'scale',function(w,h){
    div1.innerHTML = w + ',' + h;
});

每当点击 移动方块,或者鼠标滚轮滑动时,都会触发事件(发布),订阅就会打印出相应坐标

总结

设计模式这玩意真的是要把我绕晕…不知道看完博文你对发布订阅模式有没有理解呢?

客官,打赏一下嘛~~