//取出消息类型名称
let key = Array.prototype.shift.call(arguments);
// 取出该消息对应的回调函数的集合
let fns = this.list[key];
if(!fns || fns.length === 0){
return;
}
for(let i = 0,fn; fn = fns[i++]😉{
fn.apply(this,arguments); // arguments 是发布消息时附送的参数
}
}
//小红的要求 (订阅)
houseObj.listen(‘big’,function(size){
console.log(‘小红:我要的房子是’+size+‘平米’);
})
//小绿的要求 (订阅)
houseObj.listen(‘small’,function(size){
console.log(‘小绿:我要的房子是’+size+‘平米’);
})
//执行
houseObj.trigger(‘big’,100);
houseObj.trigger(‘small’,150);
-
这样我们就会看到控制台只打印了两次,传了一个唯一的key。
-
我们知道,对于上面的代码,对于不同的用户去买房子这么一个对象houseObj 进行订阅,但是如果以后我们需要对买水果子买鞋子或者其他的对象进行订阅呢,我们如果每次都这么去写是不是会很麻烦?
-
我们需要复制上面的代码,再重新改下里面的对象代码;这样是很麻烦的。为此我们需要进行代码封装 :
// 定义一个对象
let event = {
list:{},
listen: function(key,fn){ //增加一个唯一标识key
//如果没有订阅过此消息 给该消息创建一个缓存列表
(this.list[key] || (this.list[key] = [])).push(fn)
},
trigger: function(){
//取出消息类型名称
let key = Array.prototype.shift.call(arguments);
let fns = this.list[key];
if(!fns || fns.length === 0){
return;
}
for(let i = 0,fn; fn = fns[i++]😉{
fn.apply(this,arguments); // arguments 是发布消息时附送的参数
}
}
}
//定义一个initEvent函数,这个函数使所有的普通对象都具有发布订阅功能
let initEvent = function(obj){
for(let i in event){
obj[i] = event[i];
}
};
let houseObj = {}; //发布者对象
initEvent(houseObj); //为对象添加发布-订阅功能
//小明订阅的消息
houseObj.listen(‘big’,function(size){
console.log(‘小明:我要的房子是’+size+‘平米’);
})
//小绿订阅的消息
houseObj.listen(‘small’,function(size){
console.log(‘小绿:我要的房子是’+size+‘平米’);
})
houseObj.trigger(‘big’,100);
houseObj.trigger(‘small’,150);
如上,我们只需要调用initEvent,便可以使所有对象都拥有发布订阅模式。
那么,接下来,如果某用户不想订阅了
如何取消订阅?
//删除订阅
event.remove = function(key,fn){
let fns = this.list[key];
//如果没有定阅过 直接返回false
if(!fns){
return false;
}
// 如果没有传入具体的回调函数,表示需要取消key对应消息的所有订阅
if(!fn){
fn && (fns.length = 0);
}else{
for(let i = fns.length - 1;i >= 0; i-- ){
let _fn = fns[i];
_fn === fn && (fns.splice(i,1)); //删除订阅者对应的回调函数
}
}
}
//其余保持不变
//小明订阅的第一条消息
houseObj.listen(‘big’,fn1 = function(size){
console.log(‘小明:我要的第一套房子是’+size+‘平米’);
})
//小明订阅的第二条消息
houseObj.listen(‘big’,fn2 = function(size){
console.log(‘小明:我要的第二套房子是’+size+‘平米’);
})
//删除第二条
houseObj.remove(‘big’,fn2);
houseObj.trigger(‘big’,100);
这样,控制台就会只有一条打印了,只有第一条消息还在。
继续深度解耦
1.我们给每个发布者对象都添加了 listen 和 trigger 方法,以及一个缓存列表 list,这其实是一种资源浪费。
2.小明跟售楼处对象还是存在一定的耦合性,小明至少要知道售楼处对象的名字是houseObj ,要知道房型是big,还是small或是normal才能顺利的订阅到事件。
所以我们继续优化,封装一个全局发布-订阅模式对象
let Event = (function(){
let list = {},
listen,
trigger,
remove;
listen = function(key,fn){
(list[key] || (list[key] = [])).push(fn);
};
trigger = function(){
let key = Array.prototype.shift.call(arguments),
// 取出该消息对应的回调函数的集合
fns = list[key];
if(!fns || fns.length === 0){
return false;
}
for(let i = 0,fn; fn = fns[i++]😉{
fn.apply(this,arguments); // arguments 是发布消息时附送的参数
}
};
remove = function(key,fn){
let fns = list[key];
//如果没有定阅过 直接返回false
if(!fns){
return false;
}
// 如果没有传入具体的回调函数,表示需要取消key对应消息的所有订阅
// 小明在售楼处不买了 取消了 说要买三室一厅,四室一厅,结果都是吹牛皮
if(!fn){
fn && (fns.length = 0);
}else{
for(let i = fns.length - 1;i >= 0; i-- ){
let _fn = fns[i];
_fn === fn && (fns.splice(i,1)); //删除订阅者对应的回调函数
}
}
};
return {
listen:listen,
trigger:trigger,
remove:remove
}