观察者模式:
这是一种创建松散耦合代码的技术。它定义对象间 一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。由主体和观察者组成,主体负责发布事件,同时观察者通过订阅这些事件来观察该主体。主体并不知道观察者的任何事情,观察者知道主体并能注册事件的回调函数。可以广泛应用于异步编程,它可以代替我们传统的回调函数
观察者模式的优点:
我们不需要关注对象在异步执行阶段的内部状态,我们只关心事件完成的时间点
取代对象之间硬编码通知机制,一个对象不必显式调用另一个对象的接口,而是松耦合的联系在一起
虽然不知道彼此的细节,但不影响相互通信。更重要的是,其中一个对象改变不会影响另一个对象
缺点 :
创建这个函数同样需要内存,过度使用会导致难以跟踪维护
例子:
假如我们正在开发一个商城网站,网站里有header头部、nav导航、消息列表、购物车等模块。这几个模块的渲染有一个共同的前提条件,就是必须先用ajax异步请求获取用户的登录信息。这是很正常的,比如用户的名字和头像要显示在header模块里,而这两个字段都来自用户登录后返回的信息。这个时候,我们就可以把这几个模块的渲染事件都放到一个数组里面,然后待登录成功之后再遍历这个数组并且调用每一个方法。
基本模式:
function EventTarget(){
this.handlers = {};
}
EventTarget.prototype = {
constructor: EventTarget,
addHandler: function(type, handler){
if (typeof this.handlers[type] == "undefined"){
this.handlers[type] = [];
}
this.handlers[type].push(handler);
},
fire: function(event){
if (!event.target){
event.target = this;
}
if (this.handlers[event.type] instanceof Array){
var handlers = this.handlers[event.type];
for (var i=0, len=handlers.length; i < len; i++){
handlers[i](event);
}
}
},
removeHandler: function(type, handler){
if (this.handlers[type] instanceof Array){
var handlers = this.handlers[type];
for (var i=0, len=handlers.length; i < len; i++){
if (handlers[i] === handler){
break;
}
}
handlers.splice(i, 1);
}
}
};
以发布订阅的角度来思考:
var Event = (function(){
var list = {},
listen,
trigger,
remove;
listen = function(key,fn){ //监听事件函数
if(!list[key]){
list[key] = []; //如果事件列表中还没有key值命名空间,创建
}
list[key].push(fn); //将回调函数推入对象的“键”对应的“值”回调数组
};
trigger = function(){ //触发事件函数
var key = Array.prototype.shift.call(arguments); //第一个参数指定“键”
msg = list[key];
if(!msg || msg.length === 0){
return false; //如果回调数组不存在或为空则返回false
}
for(var i = 0; i < msg.length; i++){
msg[i].apply(this, arguments); //循环回调数组执行回调函数
}
};
remove = function(key, fn){ //移除事件函数
var msg = list[key];
if(!msg){
return false; //事件不存在直接返回false
}
if(!fn){
delete list[key]; //如果没有后续参数,则删除整个回调数组
}else{
for(var i = 0; i < msg.length; i++){
if(fn === msg[i]){
msg.splice(i, 1); //删除特定回调数组中的回调函数
}
}
}
};
return {
listen: listen,
trigger: trigger,
remove: remove
}
})();
var fn = function(data){
console.log(data + '的推送消息:xxxxxx......');
}
Event.listen('某公众号', fn);
Event.trigger('某公众号', '2016.11.26');
Event.remove('某公众号', fn);
题目:
// 测试1
Event.on('test', function (result) {
console.log(result);
});
Event.on('test', function () {
console.log('test');
});
Event.emit('test', 'hello world'); // 输出 'hello world' 和 'test'
意思就是,定义一个叫'test'类型的事件集,并且注册了两个test事件。然后调用test事件集里面的全部方法。在这里on方法等价于addHandle方法,emit方法等价于fire方法。其中第一个参数就是事件类型,第二个参数就是要传进函数的参数。
var Event = {
// 通过on接口监听事件eventName
// 如果事件eventName被触发,则执行callback回调函数
on: function (eventName, callback) {
//我的代码
if(!this.handles){
this.handles={};
}
if(!this.handles[eventName]){
this.handles[eventName]=[];
}
this.handles[eventName].push(callback);
},
// 触发事件 eventName
emit: function (eventName) {
//你的代码
if(this.handles[arguments[0]]){
for(var i=0;i<this.handles[arguments[0]].length;i++){
this.handles[arguments[0]][i](arguments[1]);
}
}
}
};
var person1 = {};var person2 = {};
Object.assign(person1, Event);
Object.assign(person2, Event);
person1.on('call1', function () {
console.log('person1');
});
person2.on('call2', function () {
console.log('person2');
});
person1.emit('call1'); // 输出 'person1'
person1.emit('call2'); // 没有输出
person2.emit('call1'); // 没有输出
person2.emit('call2'); // 输出 'person2'
大概意思就是为两个不同person注册自定义事件,并且两个person之间是互相独立的。
解释一下,Object.assign(person1, Event);
这个是ES6的新对象方法,用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。
意思是将Event里面的可枚举的对象和方法放到person1里面。
也就是说,如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用。由于进行测试一的时候调用了on方法,所以event里面已经有了handles这个可枚举的属性。然后再分别合并到两个person里面的话,两个person对象里面的handles都只是一个引用。所以就互相影响了
所以,我们必须将handles这个属性定义为不可枚举的,然后在person调用on方法的时候再分别产生handles这个对象。
也就是说正确的做法应该是:
var Event = {
// 通过on接口监听事件eventName
// 如果事件eventName被触发,则执行callback回调函数
on: function (eventName, callback) {
//你的代码
if(!this.handles){
//this.handles={};
Object.defineProperty(this, "handles", {
value: {},
enumerable: false,
configurable: true,
writable: true
})
}
if(!this.handles[eventName]){
this.handles[eventName]=[];
}
this.handles[eventName].push(callback);
},
// 触发事件 eventName
emit: function (eventName) {
//你的代码
if(this.handles[arguments[0]]){
for(var i=0;i<this.handles[arguments[0]].length;i++){
this.handles[arguments[0]][i](arguments[1]);
}
}
}
};