观察者模式的重要性无容置疑,作为一名前端工程师假如你只学一个设计模式的话,那么毫无疑问应该是观察者模式。
观察者模式:也被称为发布订阅模式(Publish/Subscribe),它定义了一种一对多的关系,让多个观察者对象同时监听某一个主题对象,这个主题对象的状态发生变化时就会通知所有的观察者对象,使得它们能够自动更新自己。
早些时候,我们订阅报纸,订阅牛奶等,只要我们交了钱,每天早上小哥骑着自行车给我们送报纸,牛奶。
我们以一个简单的例子来了解一下观察者模式。
在react的开发过程中,我们经常会使用redux,那么redux中就使用了观察者模式。(没有使用过也不要紧,我们简单实现一个redux)
function createStore (store) {
let curState, listnerCallBack = [];
function getState() {
return curState;
}
function subscribe(cb) {//订阅,将回调函数存入
listnerCallBack.push(cb);
}
function dispatch(action) {//当有action发送时,那么就回触发已经订阅的所有函数
curState = store(curState, action);
listnerCallBack.map( v => v() );
}
dispatch({ type: "@cyl-redux" });
return { getState, subscribe, dispatch}
}
const { subscribe, dispatch } = createStore(()=>{});
subscribe(function () {
console.log("hello");
})
subscribe(function () {
console.log("CYl");
})
dispatch({});
createStore里面的代码非常简单,我们重点看subscribe和dispatch方法。
subscribe方法是往监听回调函数里面添加方法,当执行dispatch时执行所有注册的回调函数。
dom事件监听也是一个观察者模式。
const h1 = document.getElementById("title");
h1.addEventListener("click", function (){
console.log("1");
})
h1.addEventListener("click", function (){
console.log("2");
})
h1.addEventListener("click", function (){
console.log("3");
})
当我们点击h1时,那么就会调用这个三个方法
下个例子就是nodeJS里面的event事件对象
const { EventEmitter } = require("events");
class Demo extends EventEmitter {
}
const d = new Demo();
d.on("data", function () {
console.log("cyl")
})
d.emit("data");
使用on监听 使用emit发布。
我们现在使用原生JS来模拟一下EventEmitter里面的方法,看一下观察者模式的应用
function EventEmitter () {
this.event = {};
}
//给类型type增加回调函数 callBack
EventEmitter.prototype.addListener = EventEmitter.prototype.on = function (type, callBack) {
if (this.event[type]) { //如果之前存在这个类型的事件,就继续添加
this.event[type].push(callBack);
} else {
this.event[type] = [callBack]; //如果不存在,那么就新生成一个数组
}
}
//调用类型type的所有的回调函数 callBack
EventEmitter.prototype.emit = function (type, ...res) { //参数是从第二个开始
if (this.event[type]) { //如果存在这个类型的回调函数,那么就执行
this.event[type].forEach( (listener) => {
listener.apply(this, res); //调用函数
})
}
}
//给类型type增加回调函数 callBack 但是只调用一次
EventEmitter.prototype.once = function (type, listener) {
function wrap(...res) {//接受参数的
listener.apply(this, res); //执行 然后立马销毁
this.removeListener(type, wrap);
}
this.on(type, wrap);
}
//移除类型为type,的listener
EventEmitter.prototype.removeListener = function (type, listener) {
if (this.event[type]) {
this.event[type] = this.event[type].filter( (item) => item!==listener) //true保留
}
}
//移除类型为type,所有的listner
EventEmitter.prototype.removeAllListener = function (type) {
if (this.event[type]) {
this.event[type] = [];
}
}
EventEmitter.prototype.listeners = function (type) {
return this.event[type];
}
还有比如NodeJS的流,也使用观察者模式,使用过nodeJS的应该马上就可以想起来了。
最后我们使用观察者模式来实现数据绑定。
熟悉react或者vue的前端工程师都知道,react和vue不需要我们关注视图里面的数据更新,当store里面的数据更新时,那么视图就会自动刷新。
现在我们就使用原生JS利用观察者模式来实现这个功能。
const obj = { name: "cyl" };
class Dep {
constructor () {
this.listenCallBack = [];
}
add (fn) {
this.listenCallBack.push(fn);
}
notify () {
this.listenCallBack.map( (v) => { v.update(); } );
}
}
Dep.target = null;
class Watcher {
constructor(obj, key, callBack) {
Dep.target = this;
this.obj = obj;
this.key = this.key;
this.value = obj[key];//触发get 将当前的this也就是watcher保存到listenCallBack
this.called = callBack;
Dep.target = null;
}
update () {
this.value = this.obj[this.key];
this.called();
}
}
function observe(obj) {
if (typeof obj === "object" && obj !== null) {
for (const key in obj) { //对每个属性进行监听
defineObserve(obj, key, obj[key])
}
}
}
function defineObserve(obj, key, value) {
observe(obj[key]);
const dep = new Dep();
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: () => {
if (Dep.target) {
dep.add(Dep.target); //添加当前的watcher
}
return value;
},
set: (newValue) => {
value = newValue;
dep.notify(); //当被重新设置的时候 那么就调用回调函数
}
})
}
observe(obj);
const h1 = document.getElementById("title");
new Watcher(obj, "name", () => {
h1.innerText = obj.name;
})
h1.innerText = obj.name;
setTimeout(() => {
obj.name = 6;
}, 1000)
觉得自己能力不错,可以去理解上面代码,我写了一些关键注释
我们可以看到一开始页面的显示的是cyl
1秒之后 变成了 6
观察者的使用场合就是:当一个对象的改变需要同时改变其它对象,并且它不知道具体有多少对象需要改变的时候,就应该考虑使用观察者模式。
这是一个很重要的设计模式,在前端JS中非常广泛。
大家可以看左边个人分类的目录,跟着一起学习