观察者模式
又称发布-订阅模式,指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。
应用场景
在 JavaScript 中,观察者模式最主要是应用在事件模型中:
事件模型可以分为两种,首先是原生的 Dom 事件,比如 click,这就是观察者模式的应用,相信大家一定使用过,在此就不再赘述。
其次是自定义事件,随着Vue、Angular等框架的流行,自定义事件也一定不会陌生,我们可以自己写一个自定义事件,来更好的理解观察者模式。
比如,有个购物车的功能,当点击增加或减少商品数量的按钮时,其相对应的数值和价格也会改变。
首先,我们创建一个观察者对象的基本模型:一个缓存列表,和三个方法,分别为订阅方法、发布方法以及取消方法。
const event = function () {
// 缓存列表
const _list = {};
return {
// 订阅方法
listen() {},
// 发布方法
dispatch() {},
// 取消方法
remove() {},
};
};
接下来,就是实现这三个方法。
订阅方法:
// 参数 type:自定义事件名称
// 参数 fn:与 type 相对应的处理方法
listen(type, fn) {
// 如果缓存列表中没有该事件,则注册一个
if (Object.is(_list[type], undefined)) {
_list[type] = [];
}
// 如果缓存列表中有该事件,则把处理方法保存到队列中
_list[type].push(fn);
}
发布方法:
// 参数 type:自定义事件名称
// 参数 args:函数参数
dispatch(type, ...args) {
// 如果缓存列表中没有该事件或者该事件没有可执行的方法,则退出
if (!_list[type] || _list[type].length === 0) {
return false;
}
// 否则依次执行该事件的处理方法
for (let fn of _list[type]) {
fn(...args);
}
}
取消方法:
// 参数 type:自定义事件名称
// 参数 fn:与 type 相对应的处理方法
remove(type, fn) {
// 如果缓存列表中有该事件,并且该事件有可执行的方法,则进行以下操作
if (Array.isArray(_list[type]) && _list[type].length > 0) {
// 如果没有指定某个处理方法,则全部清空
if (!fn) {
_list[type] = [];
} else {
// 如果指定了取消某个处理方法,则相应删除
_list[type] = _list[type].filter((v) => {
return v !== fn;
});
}
}
}
最后,我们去实现购物车的功能:
例子:
<button id="start">开始编辑</button>
<br />
<br />
<br />
<button id="inc" disabled>+</button>
<span>台灯</span>
<button id="dec" disabled>-</button>
<p>数量:<strong id="amount">1</strong></p>
<p>价格:<strong id="price">50</strong></p>
<br />
<br />
<br />
<button id="stop" disabled>完成</button>
<script>
const $event = (function () {
// 缓存列表
const _list = {};
return {
// 订阅方法
listen(type, fn) {
if (Object.is(_list[type], undefined)) {
_list[type] = [];
}
_list[type].push(fn);
},
// 发布方法
dispatch(type, ...args) {
if (!_list[type] || _list[type].length === 0) {
return false;
}
for (let fn of _list[type]) {
fn(...args);
}
},
// 取消方法
remove(type, fn) {
if (Array.isArray(_list[type]) && _list[type].length > 0) {
if (!fn) {
_list[type] = [];
} else {
_list[type] = _list[type].filter((v) => {
return v !== fn;
});
}
}
},
// 观察缓存列表的数据变化
show() {
console.log(_list);
},
};
})();
const startBtn = document.querySelector('#start');
const stopBtn = document.querySelector('#stop');
const incBtn = document.querySelector('#inc');
const decBtn = document.querySelector('#dec');
const amount = document.querySelector('#amount');
const price = document.querySelector('#price');
let amountNum = 1;
let priceNum = 50;
const incFn = (n) => {
amountNum++;
priceNum = amountNum * 50;
amount.innerHTML = amountNum;
price.innerHTML = priceNum;
console.log(n);
};
const decFn = (n) => {
if (amountNum === 1) {
return false;
}
amountNum--;
priceNum = priceNum - 50;
amount.innerHTML = amountNum;
price.innerHTML = priceNum;
console.log(n);
};
const changeBtnStates = (items = [startBtn, stopBtn, incBtn, decBtn]) => {
items.forEach((item) => {
typeof item.getAttribute('disabled') === 'string'
? item.removeAttribute('disabled')
: item.setAttribute('disabled', 'disabled');
});
};
// 点击“开始编辑”按钮,订阅消息
startBtn.addEventListener('click', () => {
// 订阅自定义事件 inc
$event.listen('inc', incFn);
// 订阅自定义事件 dec
$event.listen('dec', decFn);
// 其他代码,不是重点
$event.show();
changeBtnStates();
});
// 点击“+”按钮,发布(触发)自定义事件 inc
incBtn.addEventListener('click', () => {
$event.dispatch('inc', '+1');
});
// 点击“-”按钮,发布(触发)自定义事件 dec
decBtn.addEventListener('click', () => {
$event.dispatch('dec', '-1');
});
// 点击“完成”按钮,取消订阅
stopBtn.addEventListener('click', () => {
// 取消自定义事件 inc
$event.remove('inc', incFn);
// 取消自定义事件 dec
$event.remove('dec', decFn);
// 其他代码,不是重点
$event.show();
changeBtnStates();
});
</script>
观察者模式的意义
看了上面的例子,很多人会觉得,本来简单的功能,使用观察者模式反而变得复杂,意义何在?
其实,观察者模式的意义可以从两个方面体现出来:
- 在 JavaScript 开发中,观察者模式被广泛应用在异步编程中。当我们通过 Ajax 和后台交互时,想在交互完成之后对数据做一些处理,那我们可以订阅一个事件,然后在请求完成之后发布这个事件。
- 观察者模式起到了松耦合的作用,发布者与订阅者之间可以相互通信,但又不会彼此影响,只要事件名称没有变化,就可以自由地改变它们。
如有错误,欢迎指正,本人不胜感激。