如何实现?
- 想好谁是发布者
- 给发布者一个缓存列表 用于存放回调函数来通知订阅者
- 发布消息 发布者遍历这个缓存列表 依次触发里面存放的订阅者回调函数
//先订阅
var shopObj = {}; //定义发布者
shopObj.list = []; //缓存列表 存放的是订阅的函数
//增加订阅者
shopObj.listen = function (fn) { //回调函数fn
shopObj.list.push(fn)
};
//发布消息
shopObj.trigger = function () {
for (var i = 0, fn; fn === this.list[i++];){ //???
fn.apply(this, arguments)
}
};
//监听
shopObj.listen(function (color, size) {
console.log('颜色是${color}');
console.log('尺码是${size}')
});
shopObj.listen(function (color, size) {
console.log('再次颜色是${color}');
console.log('再次尺码是${size}')
});
//触发,
//小红
shopObj.trigger('red',42);
//小明
shopObj.trigger('black',43)
打印结果可以看出:把消息发布给了所有人
//先订阅
var shopObj = {}; //定义发布者
shopObj.list = []; //缓存列表 存放的是订阅的函数
//增加订阅者
shopObj.listen = function (key, fn) { //回调函数fn
if(!this.list[key]){
this.list[key] = []
}
shopObj.list[key].push(fn)
};
//发布消息
shopObj.trigger = function () {
//对应的key取出来
var key = Array.prototype.shift.call(arguments) //argument不具备shift方法
var fns = this.list[key]
if (!fns || fns.length === 0){
return
}
for (var i = 0, fn; fn === this.list[i++];){ //???
//fn.apply(this, arguments) //用es6怎么替代?arguments是数组
fn(...arguments)
}
};
//给一个唯一key值
shopObj.listen('red',function (size) {
console.log('尺码是${size}')
});
shopObj.listen('black',function (size) {
console.log('尺码是${size}')
});
//小红
shopObj.trigger('red',42);
//小明
shopObj.trigger('black',43)
打印结果:
简单封装
var event = {
list:[],
listen: function (key, fn) { //回调函数fn
if(!this.list[key]){
this.list[key] = []
}
shopObj.list[key].push(fn)
},
trigger: function () {
//对应的key取出来
var key = Array.prototype.shift.call(arguments) //argument不具备shift方法
var fns = this.list[key]
if (!fns || fns.length === 0){
return
}
for (var i = 0, fn; fn === this.list[i++];){ //???
//fn.apply(this, arguments) //用es6怎么替代?arguments是数组
fn(...arguments)
}
}
}
//取消订阅
event.remove = function (key, fn) {
var fns = this.list[key]
if (!fns) {
return false
}
if(!fn){
fn && (fns.length = 0)
}else {
for (var i = fns.length - 1; i >= 0; i--){
var _fn = fns[i]
if (_fn === fn) {
fns.splice(i, 1)
}
}
}
}
完整的
var Event = (function () {
var list = {},
listen,
trigger,
remove;
listen = function (key, fn) { //回调函数fn
if(!this.list[key]){
this.list[key] = []
}
shopObj.list[key].push(fn)
}
trigger = function () {
//对应的key取出来
var key = Array.prototype.shift.call(arguments) //argument不具备shift方法
var fns = this.list[key]
if (!fns || fns.length === 0){
return
}
for (var i = 0, fn; fn === this.list[i++];){ //???
//fn.apply(this, arguments) //用es6怎么替代?arguments是数组
fn(...arguments)
}
}
remove = function (key, fn) {
var fns = this.list[key]
if (!fns) {
return false
}
if(!fn){
fn && (fns.length = 0)
}else {
for (var i = fns.length - 1; i >= 0; i--){
var _fn = fns[i]
if (_fn === fn) {
fns.splice(i, 1)
}
}
}
}
return {
listen,
trigger,
remove
}
})()
改进异步操作中的强耦合
业务场景
加入正在开发一个商城网站,网站里有header头部、nav导航、消息列表、购物车等模块。这几个模块的渲染有一个共同的前提条件,就是必须先用ajax异步请求获取用户的登录信息。
这是很正常的,比如用户的名字和头像要显示在header模块里,而这两个字段都来自用户登陆后返回的信息
//伪代码
login.succ(function(data){
header.setAvatar(data.avatar); //设置header 模块的头像
nav.setAvatar(data.avatar);//设置导航模块的头像
message.refresh(); //刷新消息列表
cart.refresh();//刷新购物车列表
});
这种缺点:耦合度太高
发布订阅模式 实现低耦合
用发布-订阅模式重写之后,对用户信息感兴趣的业务模块将自行订阅登录成功的消息事件。当登录成功时,登录模块只需要发布登录成功的小小,而业务方接受到消息之后,就会开始进行各自的业务处理,登录模块并不关心业务方究竟要做什么,也不想了解他们的内部细节
$.ajax('http://xx.com?login', function (data) {//登录成功
login.trigger('loginSucc',data); //发布登录成功的消息
})
//各模块鉴定登录成功的消息
var header = (function () { //header模块
login.listen('loginSucc',function (data) {
header.setAvatar(data.avatar);
});
return{
setAvatar: function (data) {
console.log('设置header 模块的头像');
}
}
})();
var nav = (function () { //nav模块
login.listen('loginSucc',function (data) {
nav.setAvatar(data.avatar);
});
return{
setAvatar:function (avatar) {
console.log('设置nav 模块的头像');
}
}
})();