200字范文,内容丰富有趣,生活中的好帮手!
200字范文 > 浅析javascript观察者模式(发布-订阅模式)与应用

浅析javascript观察者模式(发布-订阅模式)与应用

时间:2021-12-28 19:15:28

相关推荐

浅析javascript观察者模式(发布-订阅模式)与应用

观察者模式(Observer):又称作发布-订阅模式或消息机制,定义了一种依赖关系,解决了主体对象与观察者之间功能的耦合。

发布订阅模式可以解决主体对象与观察者之间功能的耦合。

举个栗子,一架飞机要从沈阳飞到香港,当经过北京中转站时,需要向卫星发送位置信息,卫星接收到飞机的位置信息后将消息保存在消息容器中,向订阅了飞机信息的北京站和香港站发送信息,两个站点接收到飞机的消息并做相应的处理以避免飞机事故的发生

当飞机已经离开北京中转站,北京中转站就不需要再接收飞机的位置信息,因此北京中转站可以取消订阅飞机信息。

根据以上的例子,我们基本可以确定观察者对象的实现:

创建观察者对象为观察者对象添加消息容器新增消息订阅方法新增发送订阅的消息方法新增取消订阅的消息方法

var Observer = (function() {// 防止消息队列暴露而被篡改故将消息容器作为静态私有变量保存var _message = {};return {// 注册消息接口regist: function() {},// 发布消息接口fire: function() {},// 移除信息接口remove: function() {}}})();

下面我们来一一实现这三个方法

注册消息接口

注册方法的作用是将订阅者注册的消息推入到消息队列中,因此我们需要接收两个参数:消息类型以及相应动作

在推入到消息队列时:

如果此消息不存在则应该创建一个该消息类型并将该消息放入消息队列中如果此消息存在则应该将该消息执行方法推入该消息对应的执行方法队列中,这么做的目的也是保证多个模块注册同一则消息时能顺利执行

// 注册消息接口regist: function(type, fn) {// 如果此消息不存在则应该创建一个该消息类型if (typeof _message[type] === 'undefined') {// 将动作推入到该消息对应的动作执行队列中_message[type] = [fn];// 如果此消息存在} else {// 将动作方法推入该消息对应的动作执行序列中_message[type].push(fn);}return this;}

发布消息接口

对于发布消息方法,其功能是当观察者发布一个消息时将所有订阅者订阅的消息一次执行。

故应该接受两个参数,消息类型以及动作执行时需要传递的参数,当然在这里消息类型是必须的。在执行消息队列之前校验消息的存在是很有必要的。

然后遍历消息执行方法队列,并依此执行。然后将消息类别以及传递的参数打包后依次传入消息队列执行方法中。

// 发布消息接口fire: function(type, args) {// 如果该消息没有被注册,则返回if (!_message[type]) return;// 定义消息信息var events = {type: type,// 消息类型args: args || {}// 消息携带数据},i = 0, // 消息动作循环变量len = _message[type].length;// 消息动作长度// 遍历消息动作for(; i < len; i++) {// 依次执行注册的消息对应的动作序列_message[type][i].call(this, events);}},

消息注销接口

消息注销接口的作用是将订阅者注销的消息从消息队列清除,因此我们也需要两个参数,即消息类型以及执行的某一个动作

当然为了避免删除消息动作时消息不存在情况的出现,对消息队列中消息的存在性校验也很有必要的。

// 移除信息接口remove: function(type, fn) {// 如果消息动作队列存在if (_messages[type] instanceof Array) {// 从最后一个消息动作遍历var i = _messages[type].length - 1;for (; i >= 0; i--) {// 如果存在该动作则在消息动作中移除相应动作_messages[type][i] === fn && _messages[type].splice(i, 1);}}}

至此,我们的观察者对象(消息系统)已经创建成功,下面我们简单测试下

Observer.regist('test', function(e) {console.log(e.type, e.args.msg);});Observer.fire('test', {msg: '传递参数'});// test 传递参数

应用案例

假如我们有一个正在开发的新闻模块,需求大致如下:

当用户发布评论时,会在评论展示模块末尾处追加新的评论,与此同时用户的消息模块的消息数量也会递增用户删除留言区的信息时,用户的消息模块消息数量也会递减

但现在有一个问题,这些模块的代码是三位不同的工程师在开发,都写在各自独立的闭包模块里,导致三个模块严重耦合在一起。

文章开始,我们提到:发布订阅模式可以解决主体对象与观察者之间功能的耦合。现在,我们可以利用我们之前创建的消息系统解决模块间耦合的问题。

首先,我们来分析一下三个模块间的角色:发布留言与删除留言功能需求是用户主动触发,所以应该是观察者发布消息;追加评论以及用户消息的递增是被动触发的,所以他们是订阅者去注册消息,那么我们得出以下结论

用户信息模块既是消息的发送者也是消息的接受者提交模块是信息的发送者浏览模块是信息的接收者

// 外观模式,简化获取元素function $(id) {return document.getElementById(id);}// 工程师A(function() {// 追加一则消息function addMsgItem(e) {var text = e.args.text,// 获取消息中用户添加的文本内容ul = $('msg'),// 留言容器元素li = document.createElement('li'),// 创建内容容器元素span = document.createElement('span');// 删除按钮// 写入评论li.innerHTML = text; // 关闭按钮span.onclick = function() {ul.removeChild(li);// 发布删除留言信息Observer.fire('removeCommentMessage', {num: -1});}// 添加删除按钮li.appendChild(span);// 添加留言节点ul.appendChild(li);}Observer.regist('addCommentMessage', addMsgItem);})();

实现递增用户信息功能也很容易,只需要在原信息数目基础上加1即可

// 工程师B(function() {// 更改用户消息数目function changeMsgNum(e) {// 获取需要增加的用户消息数目var num = e.args.num;$('msg_num').innerHTML = parseInt($('msg_num').innerHTML) + num;}// 注册添加评论信息Observer.regist('addCommentMessage', changeMsgNum).regist('removeCommentMessage', changeMsgNum)})();

最后,对于一个用户来说,当他提交信息时,就要触发消息发布功能

// 工程师C (function() {// 用户点击提交按钮$('user_submit').onclick = function() {// 获取用户输入框中的内容var text = $('user_input');// 如果消息为空则提交失败if (text.value === '') return;// 发布一则评论消息Observer.fire('addCommentMessage', {text: text.value, // 消息评论内容num: 1 // 消息评论数目});text.value = ''; // 将输入框清空}})();

效果

附完整代码,为了简化流程,这里将所有js代码都放在一个文件中,实际开发中可能是多个文件

index.js

var Observer = (function() {// 防止消息队列暴露而被篡改故将消息容器作为静态私有变量保存var _message = {};return {// 注册消息接口regist: function(type, fn) {// 如果此消息不存在则应该创建一个该消息类型if (typeof _message[type] === 'undefined') {// 将动作推入到该消息对应的动作执行队列中_message[type] = [fn];// 如果此消息存在} else {// 将动作方法推入该消息对应的动作执行序列中_message[type].push(fn);}return this;},// 发布消息接口fire: function(type, args) {// 如果该消息没有被注册,则返回if (!_message[type]) return;// 定义消息信息var events = {type: type, // 消息类型args: args || {} // 消息携带数据},i = 0, // 消息动作循环变量len = _message[type].length; // 消息动作长度// 遍历消息动作for (; i < len; i++) {// 依次执行注册的消息对应的动作序列_message[type][i].call(this, events);}},// 移除信息接口remove: function(type, fn) {// 如果消息动作队列存在if (_messages[type] instanceof Array) {// 从最后一个消息动作遍历var i = _messages[type].length - 1;for (; i >= 0; i--) {// 如果存在该动作则在消息动作中移除相应动作_messages[type][i] === fn && _messages[type].splice(i, 1);}}}}})();// 外观模式,简化获取元素function $(id) {return document.getElementById(id);}// 工程师A(function() {// 追加一则消息function addMsgItem(e) {var text = e.args.text,// 获取消息中用户添加的文本内容ul = $('msg'),// 留言容器元素li = document.createElement('li'),// 创建内容容器元素span = document.createElement('span');// 删除按钮// 写入评论li.innerHTML = text; // 关闭按钮span.innerHTML = '删除';span.onclick = function() {ul.removeChild(li);// 发布删除留言信息Observer.fire('removeCommentMessage', {num: -1});}// 添加删除按钮li.appendChild(span);// 添加留言节点ul.appendChild(li);}Observer.regist('addCommentMessage', addMsgItem);})();// 工程师B(function() {// 更改用户消息数目function changeMsgNum(e) {// 获取需要增加的用户消息数目var num = e.args.num;$('msg_num').innerHTML = parseInt($('msg_num').innerHTML) + num;}// 注册添加评论信息Observer.regist('addCommentMessage', changeMsgNum).regist('removeCommentMessage', changeMsgNum)})();// 工程师C (function() {// 用户点击提交按钮$('user_submit').onclick = function() {// 获取用户输入框中的内容var text = $('user_input');// 如果消息为空则提交失败if (text.value === '') return;// 发布一则评论消息Observer.fire('addCommentMessage', {text: text.value, // 消息评论内容num: 1 // 消息评论数目});text.value = ''; // 将输入框清空}})();

index.html

<!DOCTYPE html><html><head><meta charset="utf-8"><title></title><style>body {margin: 0;}.main-wrapper {position: relative;width: 80%;margin: 0 auto;}.top-wrapper {height: 40px;line-height: 40px;background-color: #f1f1ff;padding: 0 20px;}.center-wrapper {padding: 20px;}.center-wrapper ul {list-style: none;padding: 0;}.center-wrapper li {position: relative;display: flex;justify-content: space-between;padding: 10px 15px;background: #ffdf6b;border-radius: 4px;font-size: 14px;margin-bottom: 10px;}.center-wrapper li span {font-size: 12px;cursor: pointer;color: #969696;}.center-wrapper li span:hover {color: #000;}.bottom-wrapper {padding: 20px;}.input-item {position: relative;width: 100%;height: 100px;border-radius: 3px;border: 1px solid #cecece;padding: 10px 15px;box-sizing: border-box;}.button-item {display: inline-block;position: relative;padding: 7px 20px;background: #5c96ff;font-size: 14px;color: #fff;width: 50px;border-radius: 4px;margin-top: 20px;text-align: center;}</style></head><body><div class="main-wrapper"><div class="top-wrapper"><span>黄若梅子</span><span>粉丝</span><span>7</span><span>消息</span><span id="msg_num">0</span></div><div class="center-wrapper"><h3>最新发布消息</h3><ul id="msg"></ul></div><div class="bottom-wrapper"><textarea class="input-item" id="user_input"></textarea><div class="button-item" id="user_submit">提交</div></div></div><script src="./index.js"></script></body></html>

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。