一. 发布-订阅
发布订阅模式又叫观察者模式,在某些文章中会阐述发布-订阅模式和观察者模式之间的区别,本文不阐述两者之间的具体差异,只阐述发布订阅模式的通用实现,以及如何借助该模式实现数据的变动到页面的更新。
二. 基本实现
/**
* 发布订阅(又叫观察者模式)
* 实现发布订阅模式
**/
class Event {
constructor() {
// 缓存保存回调函数
this.callbacks = {}
}
/**
* 发布事件
* @param {*} name
* @param {*} args
*
*/
$emit(name, args) {
const cbs = this.callbacks[name];
if (!cbs || cbs.length === 0) return false;
cbs.forEach(cb => cb.call(this, args))
}
/**
*
* 订阅事件
* @param {*} name
* @param {*} fn
*
*/
$on(name, fn) {
if (!this.callbacks[name]) {
this.callbacks[name] = []
}
if(typeof fn !== 'function') {
throw new Error('param:fn must be a function')
}
this.callbacks[name].push(fn)
}
/**
*
* 取消订阅事件
* @param {*} name
* @param {*} fn
*
*/
$off(name) {
this.callbacks[name] = null
}
}
三. 数据驱动视图更新
// html
<ul>
<li>苹果价格:<span class="apple">1</span>元</li>
<li>香蕉价格:<span class="banana">2</span>元</li>
</ul>
// watch方法
function watch(target) {
if (typeof target !== 'object') {
throw new Error('param must be a targetect');
}
let newTarget = {}
for (let prop in target) { //这里如果不使用let需要使用闭包
// 监听数据变化
event.$on(prop, function (val) {
// 更新视图
document.querySelector('.' + prop).innerText = val;
})
// 初始化页面数据
event.$emit(prop, target[prop])
if (target.hasOwnProperty(prop)) {
Object.defineProperty(newTarget, prop, {
get: function () {
return target[prop]
},
set: function (val) {
console.log(val);
if (val === newTarget[prop]) {
return console.log('数据未发生变化');
}
target[prop] = val
event.$emit(prop, val)
}
})
}
}
return newTarget
}
// 设置价格
let fruits = {
apple: 2,
banana: 3
}
let f2 = watch(fruits)
// 更新价格
setTimeout(() => f2.apple = 16, 2000)
四. 发布订阅模式应用
发布订阅模式主要用于js异步操作的场景
-
vue
等mvvm
框架的响应式实现 -
js
事件模型window.addEventListener(eventType, fn, false)
-
网站登录(本实例来源于javascript设计模式与开发实践8.7)
商城网站里有header头部,nav导航,消息列表购物车等模块,这几个模块的渲染有一个共同的前提条件,就是必须先用ajax异步请求获取用户的登录信息,比如用户的名字和头像要显示在header模块里,而这两个字段都来自用户登录后返回的信息。
-
传统
ajax
实现login.success(data => { header.setAvatar(data.avatar) //设置header模块的头像 nav.setAvatar(data.avatar) //设置nav模块的头像 message.refresh() //刷新消息列表 cart.refresh() // 刷新购物车列表 })
现在编写登录模块,还必须了解其他模块的变量名和方法,并且变量保持一致,这是针对具体编程实现的例子,当header模块的设置头像方法改名后,login模块也要做相应修改,这不利于代码的维护和新增功能(比如刷新收货地址)。
-
发布-订阅模式实现
// login模块 //发布登录成功的消息 $.ajax(url, data => login.trigger('loginSucc', data)) // 其他模块监听 let header = (function() { login.listen('loginSucc', data => { header.setAvatar(data.avatar) }) return { setAvatar: function(data) { console.log('设置header模块的头像') } } } )() let nav= (function() { login.listen('loginSucc', data => { nav.setAvatar(data.avatar) }) return { setAvatar: function(data) { console.log('设置nav模块的头像') } } } )() let address= (function() { login.listen('loginSucc', data => { address.refresh(data) }) return { refresh: function(data) { console.log('刷新收货地址列表') } } } )()
-
五. 总结
发布-订阅模式是js
最基本的设计模式之一,精髓在与设置一个缓存对象来保存回调函数,当触发相应事件后,执行回调函数。
可以看到使用发布订阅模式能够使我们的代码松耦合,便于代码维护和功能的拓展。
demo源码参考git:js-design-pattern