文章目录
- 1、现实中的发布-订阅模式
- [2、DOM 事件](#2、DOM 事件)
- 3、简单的发布-订阅模式
- 4、通用的发布-订阅模式
- 5、先发布再订阅
- 6、小结
发布---订阅模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。在 JavaScript 开发中,我们一般用事件模型来替代传统的发布---订阅模式
1、现实中的发布-订阅模式
小明最近看上了一套房子,到了售楼处之后才被告知,该楼盘的房子早已售罄。好在售楼
处告诉小明,不久后还有一些尾盘推出。于是小明离开之前,把电话号码留在了售楼处,相同的还有小红,小强。于是新楼盘推出的时候,售楼处会翻开花名册,遍历上面的电话号码,依次发送一条短信来通知他们
2、DOM 事件
只要我们曾经在 DOM 节点上面绑定过事件函数,那我们就曾经使用过发布---订阅模式
js
document.body.addEventListener( 'click', function(){
alert(2);
}, false );
document.body.click(); // 模拟用户点击
3、简单的发布-订阅模式
发布-订阅模式的实现步骤
1、定义发布者
2、给发布者添加一个缓存列表,用于存放回调函数以便通知订阅者
3、最后发布消息的时候,发布者会遍历这个缓存列表,依次触发里面存放的订阅者回调函数
代码示例:
js
var salesOffices = {}; // 定义发布者
salesOffices.clientList = []; // 缓存列表,存放订阅者的回调函数
salesOffices.listen = function (fn) {
// 增加订阅者
this.clientList.push(fn); // 订阅的消息添加进缓存列表
};
salesOffices.trigger = function () {
// 发布消息
for (var i = 0; i < this.clientList.length; i++) {
var fn = this.clientList.length;
fn.apply(this, arguments); // arguments 是发布消息时带上的参数
}
};
测试:
js
salesOffices.listen(function (price, squareMeter) {
// 小明订阅消息
console.log('小明价格= ' + price);
console.log('小明squareMeter= ' + squareMeter);
});
salesOffices.listen(function (price, squareMeter) {
// 小红订阅消息
console.log('小红价格= ' + price);
console.log('小红squareMeter= ' + squareMeter);
});
salesOffices.trigger(2000, 300);
salesOffices.trigger(2000, 700);
问题:
订阅者接收到了发布者发布的每个消息,有些并不是订阅者需要的
解决:
要增加一个标示 key,让订阅者只订阅自己感兴趣的消息:
改写代码:
js
var salesOffices = {}; // 定义发布者
salesOffices.clientList = {}; // 缓存对象,存放订阅者的回调函数
salesOffices.listen = function (key, fn) {
if (!this.clientList[key]) {
// 如果还没有订阅过此类消息,给该类消息创建一个缓存列表
this.clientList[key] = [];
}
this.clientList[key].push(fn); // 订阅的消息添加进消息缓存列表
};
salesOffices.trigger = function () {
// 发布消息
var key = Array.prototype.shift.call(arguments); // 取出消息类型
var fns = this.clientList[key]; // 取出该消息对应的回调函数集合
if (!fns || fns.length === 0) {
// 如果没有订阅该消息,则返回
return false;
}
for (var i = 0; i < fns.length; i++) {
var fn = fns[i];
fn.apply(this, arguments); // (2) // arguments 是发布消息时附送的参数
}
};
测试:
js
salesOffices.listen('squareMeter88', function (price) {
// 小明订阅 88 平方米房子的消息
console.log('价格= ' + price); // 输出: 2000000
});
salesOffices.listen('squareMeter110', function (price) {
// 小红订阅 110 平方米房子的消息
console.log('价格= ' + price); // 输出: 3000000
});
salesOffices.trigger('squareMeter88', 30000);
salesOffices.trigger('squareMeter110', 70000);
4、通用的发布-订阅模式
包含:发布-订阅,取消订阅
js
var Event = {
clientList: {},
listen: function (key, fn) {
if (!this.clientList[key]) {
this.clientList[key] = [];
}
this.clientList[key].push(fn);
},
trigger: function () {
var key = Array.prototype.shift.call(arguments);
var fns = this.clientList[key];
if (!fns || fns.length === 0) {
return false;
}
for (var i = 0, fn; (fn = fns[i++]); ) {
fn.apply(this, arguments);
}
},
// 增加 remove 方法
remove(key, fn) {
var fns = this.clientList[key];
if (!fns) {
return false;
}
if (!fn) {
fns && (fns.length = 0);
} else {
for (var i = fns.length - 1; i >= 0; i--) {
var _fn = fns[i];
if (fn === _fn) {
fns.splice(i, 1);
}
}
}
},
};
测试:
js
var f1 = function (price) {
console.log('价格= ' + price);
};
Event.listen('s88', f1);
var f2 = function (price) {
console.log('价格= ' + price);
};
Event.listen('s110', f2);
Event.remove('s110', f2); // 删除订阅
Event.trigger('s88', 30000);
Event.trigger('s110', 70000);
5、先发布再订阅
应用场景:发布者发布的内容,不管订阅者在发布之前订阅,或者发布之后订阅,都可触发订阅者订阅的内容
代码:
js
var Event = (function () {
var clientList = {};
var offlineStack = {}; // 离线缓存参数
var triggerStack = {}; // 已触发trigger的参数缓存
var listen;
var trigger;
var remove;
listen = function (key, fn) {
if (!clientList[key]) {
clientList[key] = [];
}
clientList[key].push(fn);
// 如果此时订阅的事件,已经发布了,则自定触发一次订阅内容(fn)
if (triggerStack[key]) {
fn.apply(this, triggerStack[key]);
} else if (offlineStack[key]) {
// 如果是离线状态,则触发事件
fn.apply(this, offlineStack[key]);
}
};
trigger = function () {
var key = Array.prototype.shift.call(arguments);
var fns = clientList[key];
if (fns) {
// 已经有人订阅此事件,将参数缓存
//(假如有些订阅者比较晚订阅,且发布者已经发布过了,那么这个订阅者订阅的时候,自动触发一次订阅内容)
triggerStack[key] = [...arguments];
for (var i = 0; i < fns.length; i++) {
fns[i].apply(this, arguments);
}
} else {
// 表示当前还没有人订阅此事件,则先将参数缓存起来
offlineStack[key] = [...arguments];
}
};
// 取消订阅
remove = function (key, fn) {
var fns = this.clientList[key];
if (!fns) {
return false;
}
if (!fn) {
// 如果没有传入具体的回调函数,表示需要取消 key 对应消息的所有订阅
fns && (fns.length = 0);
} else {
for (var l = fns.length - 1; l >= 0; l--) {
var _fn = fns[l];
if (_fn === fn) {
fns.splice(l, 1);
}
}
}
};
return {
listen: listen,
trigger: trigger,
remove: remove,
};
})();
测试1:先订阅,再发布
js
// 先订阅
Event.listen('test1', function (a) {
console.log('我是发布之前的订阅者1:', a);
});
Event.listen('test1', function (a) {
console.log('我是发布之前的订阅者2:', a);
});
// 再发布
Event.trigger('test1', 12);
// 我是发布之前的订阅者1: 12
// 我是发布之前的订阅者2: 12
测试2:先发布,再订阅
js
// 先发布
Event.trigger('test1', 12);
// 再订阅
Event.listen('test1', function (a) {
console.log('我是发布之后的订阅者1:', a);
});
Event.listen('test1', function (a) {
console.log('我是发布之后的订阅者2:', a);
});
// 我是发布之后的订阅者1: 12
// 我是发布之后的订阅者2: 12
测试3:先订阅,再发布,再订阅
js
// 先订阅
Event.listen('lis1', function (a) {
console.log('我是发布之前的订阅者1:', a);
})
Event.listen('lis1', function (a) {
console.log('我是发布之前的订阅者2:', a);
})
// 再发布
console.log('---第1次发布');
Event.trigger('lis1', 123);
console.log('---第1次发布完成');
// 再订阅
Event.listen('lis1', function (b) {
console.log('我是发布之后的订阅者~:', b);
})
// ---第1次发布
// 我是发布之前的订阅者1: 123
// 我是发布之前的订阅者2: 123
// ---第1次发布完成
// 我是发布之后的订阅者~: 123
测试4:先发布,再订阅,再发布,再订阅
js
// 先发布
console.log('------第1次发布-------');
Event.trigger('lis1', 123);
// 再订阅
Event.listen('lis1', function (a) {
console.log('我是发布之后的订阅者1:', a);
})
Event.listen('lis1', function (a) {
console.log('我是发布之后的订阅者2:', a);
})
// 再发布
console.log('------第2次发布-------');
Event.trigger('lis1', 456);
// 再订阅
Event.listen('lis1', function (a) {
console.log('我是发布之后的再次订阅者1:', a);
})
Event.listen('lis1', function (a) {
console.log('我是发布之后的再次订阅者2:', a);
})
// ------第1次发布-------
// 我是发布之后的订阅者1: 123
// 我是发布之后的订阅者2: 123
// ------第2次发布-------
// 我是发布之后的订阅者1: 456
// 我是发布之后的订阅者2: 456
// 我是发布z之后的再次订阅者1: 456
// 我是发布z之后的再次订阅者2: 456
测试5:先订阅,再发布,再订阅,再发布
js
Event.listen('lis1', function (a) {
console.log('我是发布之前的订阅者1:', a);
})
Event.listen('lis1', function (a) {
console.log('我是发布之前的订阅者2:', a);
})
console.log('---第1次发布');
Event.trigger('lis1', 123);
console.log('---第1次发布完成');
Event.listen('lis1', function (b) {
console.log('我是发布之后的订阅者~:', b);
})
console.log('---第2次发布');
Event.trigger('lis1', 456);
console.log('---第2次发布完成');
// ---第1次发布
// 我是发布之前的订阅者1: 123
// 我是发布之前的订阅者2: 123
// ---第1次发布完成
// 我是发布之后的订阅者~: 123
// ---第2次发布
// 我是发布之前的订阅者1: 456
// 我是发布之前的订阅者2: 456
// 我是发布之后的订阅者~: 456
// ---第2次发布完成
6、小结
优点:
一为时间上的解耦,二为对象之间的解耦
缺点:
1、创建订阅者本身要消耗一定的时间和内存,而且当你订阅一个消息后,也许此消息最后都未发生,但这个订阅者会始终存在于内存中
2、如果过度使用的话,对象和对象之间的必要联系也将被深埋在背后,会导致程序难以跟踪维护和理解
应用:
应用非常广泛,既可以用在异步编程中,也可以帮助我们完成更松耦合的代码编写