一、什么是观察者模式?
观察者模式:它定义对象间一种一对多的依赖关系,当一个对象的状态发生改变的时候,所有依赖他的对象都将得到通知。
DOM事件的绑定,就是一个经典的发布-订阅模式
document.body.addEventListener('click', function () {
alert(2)
}, false)
document.body.click() // 模拟用户的点击
在这里我们需要监听用户点击document.body的动作,但是我们无法知道用户什么时候点击,所以我们订阅document.body上的click事件,当body节点被点击时,body节点会向订阅者发布这个消息。
其实vuejs的双向数据绑定:也是利用观察者模式 + Object.defineProperty()对数据进行劫持来实现双向数据绑定的。后续有时间,可以写一篇文章来分析下!
二、观察者模式的好处是什么?
在我们了解到什么是观察者模式后,那么它在编程中有哪些好处呢?
1、用于异步编程回调,代替传统的回调函数的方案
2、可以取代对象之间硬编码的通知机制,代码解耦。 (如果有用vuejs做开的朋友可能会发现,Event Bus就是利用观察者模式来实现的)
// 比如,用户登录,通知其他模块更新
// 传统做法
login.success(function(data) {
// 更新用户状态
common.updateUserInfo()
//刷新购物车
cart.update()
...
})
// 采用观察者模式
$.ajax('http://xxx.com/login', function (data) {
// 登录成功后,触发订阅事件
subsEvent.tigger('loginSuccess', data)
})
// 各模块监听
var header = function () {
subsEvent.listen('loginSuccess', function (data) {
console.log('登录成功回调', data)
})
}
var nav = function () {
subsEvent.listen('loginSuccess', function (data) {
console.log('登录成功回调', data)
})
}
三、如何实现一个观察者模式呢?
聊了这么多,那么我们自己动手实现一个简单的观察者模式吧!
var subsEvent = (function () {
var clientList= [], // 缓存订阅者回调函数
listen,
tigger,
remove;
listen = function (key, fn) {
if (!clientList[key]) {
clientList[key] = []
}
clientList[key].push(fn) // 订阅消息添加到消息缓存列表
}
tigger = function () {
var key = Array.prototype.shift.call(arguments), // 取出消息类型
fns = clientList[key] // 取出回调函数
if (!fns || fns.length === 0) {
return false
}
for (var i =0; i < fns.length; i++) {
var fn = fns[i]
fn.apply(this, arguments)
}
}
remove = function (key, fn) { // 删除订阅消息
var fns = clientList[key]
if (!fns) {
return false
}
if (!fn) { // 删除key所有的订阅
fns = []
} else {
for (var i = 0; i < fns.length; i++) { // 删除对应的订阅事件
var _fn = fns[i]
if (_fn === fn) {
fns.splice(i, 1)
}
}
}
}
return {
listen: listen,
tigger: tigger,
remove: remove
}
})()
// 订阅消息
subsEvent.listen('test1', fn1 = function (data) {
console.log('触发 test1:', data)
})
subsEvent.listen('test2', fn2 = function (data) {
console.log('触发 test2:', data)
})
// 触发订阅消息
subsEvent.tigger('test1', '大家好,我是段亮,这是我写的一个观察者模式!')
// 模拟异步场景调用
setTimeout(() => {
subsEvent.tigger('test2', '没关系, 继续加油!')
}, 1000)
// 删除订阅事件
subsEvent.remove('test2', fn2)