「这是我参与2022首次更文挑战的第27天,活动详情查看:2022首次更文挑战」。
前言
观察者模式 和 发布订阅模式,在查阅资料时,有些文章中是将两者区分开来,还有些文章直接说两者就是一样的,就是一种模式。
两者的异同
既然叫两种名称,必然存在一些差异。首先看一下两种模式的流程图,还是能够比较直观的看到差异
- 察者模式(Observer)有两个对象,观察者对象(Observer)和目标对象(Subject),观察者观察或者说是订阅(Subscribe)目标,当目标发生变化,就通知(Fire Event)观察者去做更新的操作
- 发布订阅模式(Publish/Subscribe)在这两个对象之间多了一个事件通道(Event Channel)作为中间过渡,目标对象发生了变化,不直接通知观察者,而是通过事件通道去通知观察者做更新,因此观察者也只需要订阅事件通道的消息即可
在宏观上来看,这两者都是一种比较适用于异步编程的范式,可以实现目标与观察者一对多的关系(收集依赖),在未来的某一时刻当被目标发生变化时,可以批量的通知观察者去做更新
观察者模式范式
//定义观察者模式类
function observe() {
this.message = {};
}
//注册观察者
observe.prototype.regist = function (type, fn) {
this.message[type] = fn;
}
//触发观察者
observe.prototype.fire = function (type) {
this.message[type]();
}
regist
注册事件,通过调用 fire
方法一次性去触发事件
实际应用
案例:做一个抽奖页面,每转一圈速度减慢,最终停留在中奖元素上
思路是一开始就随机计算哪个元素中奖了,要做的就是控制动画的执行,转个若干圈,最终落到中间元素上
运用观察者模式,首先需要一个运动控制模块负责抽奖页面转盘的转动, 当检测到转盘转了一圈,就去通知观察者,重新再开始转动
- 首先简单绘制一个抽奖页面,当前滚动到的元素高亮显示
<body>
<style>
// 元素
.item {
width: 50px;
height: 50px;
float: left;
background: red;
margin-right: 10px;
text-align: center;
line-height: 50px;
}
// 当前转到的元素高亮
.item-on {
border: 3px solid blue
}
</style>
<div id="app"></div>
</body>
// 初始化html,绘制页面
function htmlInit(target) {
for (var i = 0; i < COUNT; i++) {
var _div = document.createElement('div');
_div.setAttribute("class", "item");
_div.innerHTML = i + 1;
target.appendChild(_div);
_domArr.push(_div);
}
}
-
看到页面渲染出了元素
-
实现运动控制模块
// 动画执行函数
function mover(moveConfig) {
var nowIn = 0; // 当前元素
var removeNum = COUNT - 1; // 前一个元素
var timer = setInterval(function () {
if (nowIn != 0) {
removeNum = nowIn - 1;
}
// 清除前一个元素的样式
_domArr[removeNum].setAttribute('class', 'item');
// 滚动到元素添加样式
_domArr[nowIn].setAttribute('class', 'item item-on');
if(moveConfig.moveTime === 0) { // 当转到第一个元素上
clearInterval(timer)
return
}
nowIn++;
if (nowIn == moveConfig.moveTime) {
clearInterval(timer);
if (moveConfig.moveTime == COUNT) { // 当转到最后一个元素上,说明还没转完
observeOb.fire('finish'); // 通知在跑一圈
}
}
}, moveConfig.speed);
}
// 动画控制
function moveControll() {
var final = getFinal();
var _circle = Math.floor(final / 10, 0); //总圈数
var _runCircle = 0; //已跑圈数
var stopNum = final % COUNT; //最终停留元素
var _speed = 50; // 初始化速度
// 跑起来
mover({
moveTime: COUNT,
speed: _speed
});
// 注册一圈转完以后需要做的事情
observeOb.regist('finish', function () {
var _time = 0;
_speed += 50; // 速度变慢
_runCircle++;
if (_runCircle <= _circle) {
_time = COUNT;
} else { // 已经跑完多余圈数,最终停在结果元素上
_time = stopNum;
}
// 继续跑
mover({
moveTime: _time,
speed: _speed
});
})
}
-
查看最终效果
-
附上完整代码
//定义观察者模式类
function observe() {
this.message = {};
}
//注册观察者
observe.prototype.regist = function (type, fn) {
this.message[type] = fn;
}
//触发观察者
observe.prototype.fire = function (type) {
this.message[type]();
}
//实例化当前观察者对象
var observeOb = new observe();
var _domArr = []; //储存dom对象
var COUNT = 10 // 对象个数
var CIRCLE = 4 // 总圈数
// 初始化html,绘制页面
function htmlInit(target) {
for (var i = 0; i < COUNT; i++) {
var _div = document.createElement('div');
_div.setAttribute("class", "item");
_div.innerHTML = i + 1;
target.appendChild(_div);
_domArr.push(_div);
}
}
// 最终结果,一共要跑几次
function getFinal() {
var _num = Math.random() * 10 + CIRCLE * COUNT;
return Math.floor(_num, 0);
}
// 动画执行函数
function mover(moveConfig) {
var nowIn = 0;
var removeNum = COUNT - 1;
var timer = setInterval(function () {
if (nowIn != 0) {
removeNum = nowIn - 1;
}
_domArr[removeNum].setAttribute('class', 'item');
_domArr[nowIn].setAttribute('class', 'item item-on');
if(moveConfig.moveTime === 0) { //转到第一个元素上
clearInterval(timer)
return
}
nowIn++;
if (nowIn == moveConfig.moveTime) {
clearInterval(timer);
if (moveConfig.moveTime == COUNT) { // 当转到最后一个元素上,说明还没转完
observeOb.fire('finish'); // 通知在跑一圈
}
}
}, moveConfig.speed);
}
// 动画控制
function moveControll() {
var final = getFinal();
var _circle = Math.floor(final / 10, 0); //总圈数
var _runCircle = 0; //已跑圈数
var stopNum = final % COUNT; //最终停留元素
var _speed = 50; // 初始化速度
// 跑起来
mover({
moveTime: COUNT,
speed: _speed
});
// 注册一圈转完以后需要做的事情
observeOb.regist('finish', function () {
var _time = 0;
_speed += 50; // 速度变慢
_runCircle++;
if (_runCircle <= _circle) {
_time = COUNT;
} else { // 已经跑完多余圈数,最终停在结果元素上
_time = stopNum;
}
// 继续跑
mover({
moveTime: _time,
speed: _speed
});
})
}
htmlInit(document.getElementById('app')); //初始化
moveControll(); //执行动画