// 发布-订阅的功能
const event = {
clientList: {},
listen: function(key, callback) {
if (!this.clientList[key]) {
this.clientList[key] = []
}
this.clientList[key].push(callback)
},
trigger: function() {
const key = Array.prototype.shift.call(arguments)
const callbackList = this.clientList[key]
if (!callbackList || callbackList.length === 0) {
return false
}
for(var i = 0, fn; fn=callbackList[i++];){
fn.apply(this, arguments)
}
},
remove: function(key, fn) {
const callbackList = this.clientList[key]
if (!callbackList || callbackList.length === 0) {
return false
}
// 如果没有传入具体的函数,则代表清空所有
if(!fn) {
callbackList && (callbackList.length = 0)
} else {
for(var i = 0, len = callbackList.length; i < len; i++) {
if (callbackList[i] === fn) {
callbackList.splice(i, 1)
break
}
}
}
}
}
// 给所有的对象安装发布-订阅功能
var installEvent = function( obj ){
for ( var i in event ){
obj[ i ] = event[ i ];
}
};
// 给售楼处动态增加发布-订阅功能
const salesOffices = {}
installEvent(salesOffices)
// 客户订阅
const fn1 = function(price) {
console.log('squareMeter88价格-'+price)
}
salesOffices.listen('squareMeter88', fn1)
const fn11 = function() {
console.log('fjkjsdlkfjlaksd')
}
salesOffices.listen('squareMeter88', fn11)
const fn2 = function(price) {
console.log('squareMeter110价格-'+price)
}
salesOffices.listen('squareMeter110', fn2)
// 时机成熟后,发布
let num = 0
let timer = setInterval(() => {
num += 1
salesOffices.trigger('squareMeter88', 20000)
salesOffices.trigger('squareMeter110', 30000)
if (num === 3) {
salesOffices.remove( 'squareMeter88', fn1 );
salesOffices.remove( 'squareMeter110', fn2 );
} else if (num ==5) {
clearInterval(timer)
timer = null
}
}, 3000)
进一步优化
// 发布-订阅的功能
const Event = (function() {
let clientList = {}
const listen = function(key, callback) {
if (!clientList[key]) {
clientList[key] = []
}
clientList[key].push(callback)
}
const trigger = function() {
const key = Array.prototype.shift.call(arguments)
const callbackList = clientList[key]
if (!callbackList || callbackList.length === 0) {
return false
}
for(var i = 0, fn; fn=callbackList[i++];){
fn.apply(this, arguments)
}
}
const remove = function(key, fn) {
const callbackList = clientList[key]
if (!callbackList || callbackList.length === 0) {
return false
}
// 如果没有传入具体的函数,则代表清空所有
if(!fn) {
callbackList && (callbackList.length = 0)
} else {
for(var i = 0, len = callbackList.length; i < len; i++) {
if (callbackList[i] === fn) {
callbackList.splice(i, 1)
break
}
}
}
}
return {
listen,
trigger,
remove
}
})()
// 客户订阅
Event.listen('squareMeter88', fn1 = function(price) {
console.log('squareMeter88价格-'+price)
})
Event.listen('squareMeter110', fn2 = function(price) {
console.log('squareMeter110价格-'+price)
})
// 时机成熟后,发布
let num = 0
let timer = setInterval(() => {
num += 1
Event.trigger('squareMeter88', 20000)
Event.trigger('squareMeter110', 30000)
if (num === 3) {
Event.remove( 'squareMeter88', fn1 );
Event.remove( 'squareMeter110', fn2 );
clearInterval(timer)
timer = null
}
}, 3000)
用发布订阅模式处理 模块之间的通信: 比如现在有两个模块,a模块里面有一个按钮,每次点击按钮之后,b模块里的div中会显示按钮的总点击次数,我们用全局发布—订阅模式完成下面的代码,使得a模块和b模块可以在保持封装性的前提下进行通信。
<button id="count">
点我
</button>
<div id="show" style="width: 100px;height: 100px; border: 1px solid red"></div>
const a = (function() {
let num = 0
const btn = document.getElementById('count')
btn.onclick = function() {
Event.trigger('add', ++num)
}
})()
const b = (function() {
const el = document.getElementById('show')
Event.listen('add', function(count) {
el.innerText = count
})
})()
终极模式 全局的发布—订阅对象里只有一个clinetList来存放消息名和回调函数,大家都通过它来订阅和发布各种消息,久而久之,难免会出现事件名冲突的情况,所以我们还可以给Event对象提供创建命名空间的功能。(以下内容没特别理解)
var Event = (function () {
var global = this,
Event,
_default = 'default';
Event = function () {
var _listen,
_trigger,
_remove,
_slice = Array.prototype.slice,
_shift = Array.prototype.shift,
_unshift = Array.prototype.unshift,
namespaceCache = {},
_create,
find,
each = function (ary, fn) {
var ret;
for (var i = 0, l = ary.length; i < l; i++) {
var n = ary[i];
ret = fn.call(n, i, n);
}
return ret;
};
_listen = function (key, fn, cache) {
if (!cache[key]) {
cache[key] = [];
}
cache[key].push(fn);
};
_remove = function (key, cache, fn) {
if (cache[key]) {
if (fn) {
for (var i = cache[key].length; i >= 0; i--) {
if (cache[key][i] === fn) {
cache[key].splice(i, 1);
}
}
} else {
cache[key] = [];
}
}
};
_trigger = function () {
var cache = _shift.call(arguments),
key = _shift.call(arguments),
args = arguments,
_self = this,
ret,
stack = cache[key];
if (!stack || !stack.length) {
return;
}
return each(stack, function () {
return this.apply(_self, args);
});
};
_create = function (namespace) {
var namespace = namespace || _default;
var cache = {},
offlineStack = [],
ret = {
listen: function (key, fn, last) {
_listen(key, fn, cache);
if (offlineStack === null) {
return;
}
if (last === 'last') {
offlineStack.length && offlineStack.pop()();
} else {
each(offlineStack, function () {
this();
});
}
offlineStack = null;
},
one: function (key, fn, last) {
_remove(key, cache);
this.listen(key, fn, last);
},
remove: function (key, fn) {
_remove(key, cache, fn);
},
trigger: function () {
var fn,
args,
_self = this;
_unshift.call(arguments, cache);
args = arguments;
fn = function () {
return _trigger.apply(_self, args);
};
if (offlineStack) {
return offlineStack.push(fn);
}
return fn();
}
};
return namespace ?
(namespaceCache[namespace] ? namespaceCache[namespace] :
namespaceCache[namespace] = ret) :
ret;
};
return {
create: _create,
one: function (key, fn, last) {
var event = this.create();
event.one(key, fn, last);
},
remove: function (key, fn) {
var event = this.create();
event.remove(key, fn);
},
listen: function (key, fn, last) {
var event = this.create();
event.listen(key, fn, last);
},
trigger: function () {
var event = this.create();
event.trigger.apply(this, arguments);
}
};
}();
return Event;
})();
Event.create('namespace1').listen('click', function (a) {
console.log(a)
})
Event.create('namespace1').trigger('click', 1)
Event.create('namespace2').listen('click', function (a) {
console.log(a)
})
Event.create('namespace2').trigger('click', 2);
优缺点:
发布—订阅模式的优点非常明显,一为时间上的解耦,二为对象之间的解耦。从架构上来看,无论是MVC还是MVVM,都少不了发布—订阅模式的参与。
当然,发布—订阅模式也不是完全没有缺点。创建订阅者本身要消耗一定的时间和内存,而且当你订阅一个消息后,也许此消息最后都未发生,但这个订阅者会始终存在于内存中。另外,发布—订阅模式虽然可以弱化对象之间的联系,但如果过度使用的话,对象和对象之间的必要联系也将被深埋在背后,会导致程序难以跟踪维护和理解。特别是有多个发布者和订阅者嵌套到一起的时候,要跟踪一个bug不是件轻松的事情。