javascript 设计模式 发布订阅模式
发布/订阅模式(Pub/Sub)是一种消息模式,它有两个参与者:发布者和订阅者。发布者向某个信道发布
一条消息,订阅者绑定这个信道,当有消息发布至信道时就会接收到一个通知。最重要的一点是,发布者和
订阅者是完全解耦的,彼此并不知晓对方的存在。两者仅仅共享一个信道名称。
这种模式在js里面有这天然的优势,因为js本身就是事件驱动型语言。比如,页面上有一个button,
你点击一下就会触发上面的click事件,而此时有一部分程序正在监听这个事件,随之触发相关的处理程序。
事实上,我们也早就熟悉这个模式了,只是不知道这叫什么(订阅发布模式 又名 观察者模式).
这个模式最大的一个好处就在于,能够解耦回调函数,让你的程序看起来更美观(虽然现在有Promise和
一条消息,订阅者绑定这个信道,当有消息发布至信道时就会接收到一个通知。最重要的一点是,发布者和
订阅者是完全解耦的,彼此并不知晓对方的存在。两者仅仅共享一个信道名称。
这种模式在js里面有这天然的优势,因为js本身就是事件驱动型语言。比如,页面上有一个button,
你点击一下就会触发上面的click事件,而此时有一部分程序正在监听这个事件,随之触发相关的处理程序。
事实上,我们也早就熟悉这个模式了,只是不知道这叫什么(订阅发布模式 又名 观察者模式).
这个模式最大的一个好处就在于,能够解耦回调函数,让你的程序看起来更美观(虽然现在有Promise和
Deferred帮忙,但是不彻底)。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>发布订阅模式</title>
</head>
<body>
<script>
var pubsub = (function(){
var q = {}, topics = {}, subUid = -1;
//发布消息
q.publish = function(topic, args) {
if (!topics[topic]) {
return;
}
var subs = topics[topic], len = subs.length;
while(len--) {
subs[len].func(topic, args);
}
return this;
};
//订阅事件
q.subscribe = function(topic, func) {
topics[topic] = topics[topic] ? topics[topic] : [];
var token = (++subUid).toString();
topics[topic].push({
token : token,
func : func
});
return token;
};
return q;
//取消订阅就不写了,遍历topics,然后通过保存前面返回token,删除指定元素
})();
//触发的事件
var logmsg = function(topics, data) {
console.log("logging:" + topics + ":" + data);
}
//监听指定的消息'msgName'
var sub = pubsub.subscribe('msgName', logmsg);
//发布消息'msgName'
pubsub.publish('msgName', 'hello world');
//发布无人监听的消息'msgName1'
pubsub.publish('anotherMsgName', 'me too!');
</script>
</body>
功能完善的发布订阅模式:
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.bootcss.com/jquery/3.2.1/jquery.js"></script>
</head>
<body>
<script>
var $EventEmiter = (function () {
function $EventEmiter () {
this._events = {
}
}
$EventEmiter.prototype = {
on: function (eventName, callback) {
if (this._events[eventName]) {
//如果有就放一个新的
this._events[eventName].push(callback);
} else {
//如果没有就创建一个数组
this._events[eventName] = [callback];
}
},
emit: function (eventName, caller, args) {
if (this._events[eventName]) { //循环一次执行
this._events[eventName].forEach(function(item) {
item.apply(caller, args)
});
}
},
removeListener: function (eventName, callback) {
if (this._events[eventName]) {
//当前数组和传递过来的callback相等则移除掉
//this._events[eventName] = this._events[eventName].filter(item=>item!==callback);
var temp = [];
for (var i = 0; i < this._events[eventName].length; i++) {
if (this._events[eventName][i] !== callback) {
temp.push(this._events[eventName][i]);
}
}
this._events[eventName] = temp;
}
},
removeListenerByEventName: function (eventName) {
if (this._events[eventName]) {
this._events[eventName] = [];
}
},
once: function(eventName, callback) {
var that = this;
function one() {
//在one函数运行原来的函数,只有将one清空
callback.apply(this, arguments);
//先绑定 执行后再删除
that.removeListener(eventName, one);
}
this.on(eventName, one);
//此时emit触发会执行此函数,会给这个函数传递rest参数
}
}
return $EventEmiter;
})();
var EventEmiter = new $EventEmiter();
var msgName = function(data) {
console.log("msgName:" + data);
}
var sub = EventEmiter.on('msgName', msgName);
var msgNames = function(data) {
console.log("msgNames:" + data);
}
var sub = EventEmiter.on('msgName', msgNames);
EventEmiter.emit('msgName', this, ['hello world']);
EventEmiter.emit('msgName', this, ['hello world2']);
EventEmiter.removeListener('msgName', msgName);
EventEmiter.emit('msgName', this, ['hello world3']);
EventEmiter.removeListenerByEventName('msgName');
EventEmiter.emit('msgName', this, ['hello world4']);
EventEmiter.emit('msgName', this, ['hello world5']);
var msgName2 = function(data) {
console.log("msgName2:" + ":" + data);
}
var sub = EventEmiter.once('msgName2', msgName2);
EventEmiter.emit('msgName2', this, ['hello world']);
EventEmiter.emit('msgName2', this, ['hello world2']);
EventEmiter.emit('anotherMsgName', this, ['me too!']);
</script>
</body>
</html>
使用es6实现:
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.bootcss.com/jquery/3.2.1/jquery.js"></script>
</head>
<body>
<script>
//发布订阅模式
class EventEmiter{
constructor(){
//维护一个对象
this._events={
}
}
on(eventName,callback){
if( this._events[eventName]){
//如果有就放一个新的
this._events[eventName].push(callback);
}else{
//如果没有就创建一个数组
this._events[eventName]=[callback]
}
}
emit(eventName,...rest){
if(this._events[eventName]){ //循环一次执行
this._events[eventName].forEach((item)=>{
item.apply(this,rest)
});
}
}
removeListener(eventName,callback){
if(this._events[eventName]){
//当前数组和传递过来的callback相等则移除掉
this._events[eventName]=
this._events[eventName].filter(item=>item!==callback);
}
}
once(eventName,callback){
function one(){
//在one函数运行原来的函数,只有将one清空
callback.apply(this,arguments);
//先绑定 执行后再删除
this.removeListener(eventName,one);
}
this.on(eventName,one);
//此时emit触发会执行此函数,会给这个函数传递rest参数
}
}
class Man extends EventEmiter{}
let man=new Man()
function findGirl() {
console.log('找新的女朋友')
}
function saveMoney() {
console.log('省钱')
}
man.once('失恋',findGirl);
//man.on('失恋',findGirl) //失恋 ,绑定一个函数方法
man.on('失恋',saveMoney)//失恋 ,绑定一个函数方法
man.removeListener('失恋',saveMoney); //移除一个函数方法
man.emit('失恋');
//绑定一次,触发多次,也只执行一次。触发后一次将数组中的哪一项删除掉下次触发就不会执行
</script>
</body>
</html>