观察者模式也叫发布订阅模式!,为了解决主体对象与观察者之间功能的耦合。
定义:定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖对象都会收到通知自动更新。
观察者模式主要分为三个步骤:注册、发布、移除。我们一个一个步骤来实现。
我们定义一个容器,再用闭包包起来,防止污染全局变量,里面存放这些方法,以及要观察的事件。
详细解释看注解
//防止信息队列暴露而被慕改或影响到其他变量
var Observer = (function () {
var _messages = {};
return {
//注册信息接口
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 || {} //消息携带数据
},
i = 0, //消息动作循环变量
len = _messages[type].length; //消息动作长度
//遍历消息动作
for (; i < len; i++) {
//依次执行注册的消息对应的动作序列
_messages[type][i].call(this, events);
}
},
//移除信息接口
remove: function (type, fn) {
//如果消息动作队列存在
if (_message[type] instanceof Array) {
//从最后一个消息动作遍历
var i = _message[type].length - 1;
for (; i >= 0; i--) {
//如果存在该动作在消息动作序列中移除相应动作
_messages[type][i] === fn && _messages[type].splice(i, 1);
}
}
}
}
})();
上面就是我们定义好的观察者,当我们想要订阅发布某些实例的时候直接调用即可。
接着我们结合实例来看一下这是怎么使用的,例如我们做一个留言并统计浏览数量的小功能
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div>
<div>
用户的数量
<span id="msg_num">0</span>
</div>
<ul id="msg">
</ul>
<input type="text" id="user_input" />
<button id="user_submit">提交</button>
</div>
<script>
//防止信息队列暴露而被慕改或影响到其他变量
var Observer = (function () {
var _messages = {};
return {
//注册信息接口
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 || {} //消息携带数据
},
i = 0, //消息动作循环变量
len = _messages[type].length; //消息动作长度
//遍历消息动作
for (; i < len; i++) {
//依次执行注册的消息对应的动作序列
_messages[type][i].call(this, events);
}
},
//移除信息接口
remove: function (type, fn) {
//如果消息动作队列存在
if (_message[type] instanceof Array) {
//从最后一个消息动作遍历
var i = _message[type].length - 1;
for (; i >= 0; i--) {
//如果存在该动作在消息动作序列中移除相应动作
_messages[type][i] === fn && _messages[type].splice(i, 1);
}
}
}
}
})();
function $(id) {
return document.getElementById(id);
}
(function () {
//增加一则消息
function addMsgItem(e) {
var text = e.args.text, //获取消息中用户添加的文本内容
ul = $('msg'),
li = document.createElement('li'),
span = document.createElement('span');
span.innerHTML = " 关闭"
li.innerHTML = text;
span.onclick = function () { //移除留言
ul.removeChild(li);
Observer.fire('removeCommentMessage', {
num: -1
});
}
li.appendChild(span);
ul.appendChild(li);
}
Observer.regist('addCommentMessage', addMsgItem); //注册添加评论信息
})();
(function () {
function changeMsgNum(e) {
var num = e.args.num;
$('msg_num').innerHTML = parseInt($('msg_num').innerHTML) + num;
}
Observer.regist('addCommentMessage', changeMsgNum);
Observer.regist('removeCommentMessage', changeMsgNum);
})();
(function () {
$('user_submit').onclick = function () {
var text = $('user_input');
if (text.value === '') {
return;
}
Observer.fire('addCommentMessage', {
text: text.value,
num: 1
});
text.value = '';
}
})();
</script>
</body>
</html>
在这个例子中可以看出我们将每个模块抽离了出来,互不关系,完美解决模块间耦合功能。
其使用什么广泛,适用于关联于某些行为场景,并触发一系列事件。例如用来发布消息推送、点击事件侦听等等。。。