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;
});
每当点击 移动方块,或者鼠标滚轮滑动时,都会触发事件(发布),订阅就会打印出相应坐标