什么是观察者模式
观察者模式(Observer):又被称为发布-订阅模式或消息机制,定义了一种一对多的依赖关系,只要被观察对象发生改变,依赖于它的观察者对象就会收到通知并自动被更新,解决了主体对象(被观察对象)与观察者之间功能的耦合。
通俗易懂地介绍观察者模式
单看定义可能不是很明白,下面举一个通俗易懂的例子。 你到商店去买东西,刚好你要买的东西没有货了,于是你在店员那里留了电话。过了几天,店里有货了,那名店员就打电话给你,然后你接到电话就去店里面取货了。 这个例子里面,电话号码就是那条消息;你把电话号码给店员,就是注册消息;店员后来给你打电话,叫你回去取,就是发布消息;你接到电话,回去取就是观察者接受了消息,进行了后续处理。
实现观察者模式
首先,我们需要创建一个观察者对象,包括一个消息容器、3个方法,分别是订阅消息方法、发送订阅方法和取消订阅方法。
var Observer = (function () {
// 使用闭包,是为了防止消息队列被篡改
var __messages = {};
return {
// 注册消息接口
regist: function () {},
// 发布消息接口
fire: function () {},
// 移除消息接口
remove: function () {},
};
})();
注册消息方法
注册消息方法的作用是将订阅者注册的消息推入到消息队列中,因此我们需要接受两个参数,分别是消息类型和相应的处理方法。这里要分2中情况:1.如果消息类型不存在,就需要创建一个消息类型,并将处理方法放入该消息类型;2.如果消息类型存在,则直接将处理方法放入该消息类型的消息队列中。
// 注册消息方法
regist: function (type, fn) {
if (typeof __messages[type] === "undefined") {
__messages[type] = [fn];
} else {
__messages[type].push(fn);
}
}
发布消息方法
发布消息方法的功能就是当观察者发布一个消息是,将所有订阅者订阅的消息依次执行。这里也需要传递两个参数,分别是消息类型和动作执行时的参数,其中消息类型是必须的。在执行消息队列之前要校验该消息类型的消息队列是否存在,存在则依次执行消息队列中的方法。
// 发布消息方法
fire: function (type, args) {
if (!__messages[type]) {
return;
}
var events = {
type: type, // 消息类型
args: args, // 消息携带数据
},
len = __messages[type].length; // 消息队列长度
for (let i = 0; i < len; i++) {
__messages[type][i].call(this, events);
}
}
移除消息方法
移除消息方法就是将订阅者注册的消息从消息队列中清除,因此我们需要传递两个参数,即消息类型和执行队列中的某一方法。当然为了避免删除消息时,消息队列中不存在的情况,需要对消息队列中消息的存在进行校验。
// 注销消息方法
remove: function (type, fn) {
// 如果消息执行队列存在
if (__messages[type] instanceof Array) {
let index = removeMessages.indexOf(fn);
if (index){
removeMessages.splice(index, 1);
}
// 从最后一个消息方法开始遍历
// var i = __messages[type].length - 1;
// for (; i >= 0; i--) {
// __messages[type][i].toString().replace(fn, '$1') === fn.toString().replace(fn, '$1') && __messages[type].splice(i, 1);
// }
}
}
到此,我们就完成了一个观察者模型,现在就去实操一下吧。
Observer.regist("test", function (e) {
console.log(e.type, e.args.msg);
});
Observer.regist("test", function (e) {
console.log("hello", e.args.name);
});
Observer.fire("test", { msg: "传递参数1", name: "Eillen" });
/** 结果
* test 传递参数1
* hello Eillen
*/
Observer.remove("test", function (e) {
console.log("hello", e.args.name);
});
Observer.fire("test", { msg: "传递参数2", name: "Jack"});
/** 结果
* test 传递参数2
*/
至此,我们就已经实现了观察者模式,为自己点个赞吧,☺️
应用场景
redux中就应用到了观察者模式,下面的实例是redux的核心代码。如果看不懂,可以去学习一下8k字 | Redux/react-redux/redux中间件设计实现剖析中的redux知识。
import { reducer } from './reducer'
export const createStore = (reducer) => {
let currentState = {}
let listeners = [] //消息队列
function getState() {
return currentState
}
function dispatch(action) {
currentState = reducer(currentState, action)
listeners.forEach(listener => listener())
}
function subscribe(listener) {
listeners.push(listener)
return function unsubscribe(){
const index = listeners.indexOf(listener);
listeners.splice(index, 1);
}
}
dispatch({ type: '@@REDUX_INIT' }) //初始化store数据
return { getState, subscribe, dispatch }
}
解析:
subscribe就是上面register方法,用来注册消息;dispatch就是上面的fire方法,用来发布消息;subscribe执行后的unsubscribe方法就是取消订阅的方法。