发布—订阅模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状 态发生改变时,所有依赖于它的对象都将得到通知。在 JavaScript开发中,我们一般用事件模型 来替代传统的发布—订阅模式。
发布—订阅模式可以广泛应用于异步编程中,这是一种替代传递回调函数的方案。 比如,我们可以订阅 ajax请求的 error、succ 等事件。 在异步编程中 使用发布—订阅模式,我们就无需过多关注对象在异步运行期间的内部状态,而只需要订阅感兴 趣的事件发生点。
发布—订阅模式可以取代对象之间硬编码的通知机制,一个对象不用再显式地调 用另外一个对象的某个接口。发布—订阅模式让两个对象松耦合地联系在一起,虽然不太清楚彼 此的细节,但这不影响它们之间相互通信。当有新的订阅者出现时,发布者的代码不需要任何修 改;同样发布者需要改变时,也不会影响到之前的订阅者。只要之前约定的事件名没有变化,就 可以自由地改变它们。
DOM事件
document.body.addEventListener( 'click', function(){
alert(2);
}, false );
document.body.click(); // 模拟用户点击
我们订阅 document.body 上的 click 事件,当 body 节点被点击时,body 节点便会向订阅 者发布这个消息。
当然我们还可以随意增加或者删除订阅者,增加任何订阅者都不会影响发布者代码的编写
自定义事件
实现发布—订阅模式
- 首先要指定好谁充当发布者;
- 然后给发布者添加一个缓存列表,用于存放回调函数以便通知订阅者;
- 后发布消息的时候,发布者会遍历这个缓存列表,依次触发里面存放的订阅者回调函数
我们还可以往回调函数里填入一些参数,订阅者可以接收这些参数
发布-订阅模式的通用实现
//所以我们把发布—订阅的功能提取出来,放在一个单独的对象内:
var event = {
clientList: [],
listen: function (key, fn) { //key用来区分不同类型的订阅者
if (!this.clientList[key]) {
this.clientList[key] = [];
}
this.clientList[key].push(fn); // 订阅的消息添加进缓存列表
},
trigger: function () {
var key = Array.prototype.shift.call(arguments), // (1);
fns = this.clientList[key];
if (!fns || fns.length === 0) { // 如果没有绑定对应的消息
return false;
}
for (var i = 0, fn; fn = fns[i++];) {
fn.apply(this, arguments); // (2) // arguments 是trigger 时带上的参数
}
}
};
// 再定义一个 installEvent 函数,这个函数可以给所有的对象都动态安装发布—订阅功能
var installEvent = function (obj) {
for (var i in event) {
obj[i] = event[i];
}
};
//再来测试一番,我们给售楼处对象salesOffices 动态增加发布—订阅功能
var salesOffices = {};
installEvent(salesOffices);
salesOffices.listen('squareMeter88', function (price) { // 小明订阅消息
console.log('价格= ' + price);
});
salesOffices.listen('squareMeter100', function (price) { // 小红订阅消息
console.log('价格= ' + price);
});
salesOffices.trigger('squareMeter88', 2000000); // 输出:2000000
salesOffices.trigger('squareMeter100', 3000000); // 输出:3000000
取消订阅的事件
给event对象添加remove方法
event.remove = function (key, fn) {
var fns = this.clientList[key];
if (!fns) { // 如果key 对应的消息没有被人订阅,则直接返回
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); // 删除订阅者的回调函数
}
}
}
};
remove方法的使用
var salesOffices = {};
var installEvent = function (obj) {
for (var i in event) {
obj[i] = event[i];
}
}
installEvent(salesOffices);
salesOffices.listen('squareMeter88', fn1 = function (price) { // 小明订阅消息
console.log('价格= ' + price);
});
salesOffices.listen('squareMeter88', fn2 = function (price) { // 小红订阅消息
console.log('价格= ' + price);
});
salesOffices.remove('squareMeter88', fn1); // 删除小明的订阅
salesOffices.trigger('squareMeter88', 2000000); // 输出:2000000
全局的发布-订阅对象
其实就是不再使用installEvent函数,而是直接把event对象设置成全局对象Event,对象内有一个发布者trigger,通过这个对象发布不同类型的消息给对应的订阅者
var Event = (function () {
var clientList = {},
listen,
trigger,
remove;
listen = function (key, fn) {
if (!clientList[key]) {
clientList[key] = [];
}
clientList[key].push(fn);
};
trigger = function () {
var key = Array.prototype.shift.call(arguments),
fns = clientList[key];
if (!fns || fns.length === 0) {
return false;
}
for (var i = 0, fn; fn = fns[i++];) {
fn.apply(this, arguments);
}
};
remove = function (key, fn) {
var fns = clientList[key];
if (!fns) {
return false;
}
if (!fn) {
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
}
})();
Event.listen('squareMeter88', function (price) { // 小红订阅消息
console.log('价格= ' + price); // 输出:'价格=2000000'
});
Event.trigger('squareMeter88', 2000000); // 售楼处发布消息