前一段时间一直在写CSS3的文章 
一直都没写设计模式 
今天来写写大名鼎鼎观察者模式 
先画张图

观察者模式的理解

我觉得还是发布-订阅模式的叫法更容易我们理解 
(不过也有的书上认为它们是两种模式……) 
这就类似我们在微信平台订阅了公众号 
当它有新的文章发表后,就会推送给我们所有订阅的人

我们可以看到例子中这种模式的优点

  • 我们作为订阅者不必每次都去查看这个公众号有没有新文章发布, 
    公众号作为发布者会在合适时间通知我们
  • 我们与公众号之间不再强耦合在一起。公众号不关心谁订阅了它, 
    不管你是男是女还是宠物狗,它只需要定时向所有订阅者发布消息即可

很简单的道理,过年的时候,群发祝福短信一定要比挨个发短信方便的多

通过上面的例子映射出我们观察者模式的优点

  • 可以广泛应用于异步编程,它可以代替我们传统的回调函数 
    我们不需要关注对象在异步执行阶段的内部状态,我们只关心事件完成的时间点
  • 取代对象之间硬编码通知机制,一个对象不必显式调用另一个对象的接口,而是松耦合的联系在一起 
    虽然不知道彼此的细节,但不影响相互通信。更重要的是,其中一个对象改变不会影响另一个对象

可能看完这些一脸懵逼,不过没关系 
下面我们会深入体会它的优点

自定义事件

其实观察者模式我们都曾使用过,就是我们熟悉的事件 
但是内置的事件很多时候不能满足我们的要求 
所以我们需要自定义事件


现在我们想实现这样的功能 
定义一个事件对象,它有以下功能

  • 监听事件(订阅公众号)
  • 触发事件(公众号发布)
  • 移除事件(取订公众号)

当然我们不可能只订阅一个公众号,可能会有很多 
所以我们要针对不同的事件设置不同的”键” 
这样我们储存事件的结构应该是这样的

//伪代码
Event = {
name1: [回调函数1,回调函数2,...],
name2: [回调函数1,回调函数2,...],
name3: [回调函数1,回调函数2,...],
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

代码如下

var Event = (function(){
var list = {},
listen,
trigger,
remove;
listen = function(key,fn){ //监听事件函数
if(!list[key]){
list[key] = []; //如果事件列表中还没有key值命名空间,创建
}
list[key].push(fn); //将回调函数推入对象的“键”对应的“值”回调数组
};
trigger = function(){ //触发事件函数
var key = Array.prototype.shift.call(arguments); //第一个参数指定“键”
msg = list[key];
if(!msg || msg.length === 0){
return false; //如果回调数组不存在或为空则返回false
}
for(var i = 0; i < msg.length; i++){
msg[i].apply(this, arguments); //循环回调数组执行回调函数
}
};
remove = function(key, fn){ //移除事件函数
var msg = list[key];
if(!msg){
return false; //事件不存在直接返回false
}
if(!fn){
delete list[key]; //如果没有后续参数,则删除整个回调数组
}else{
for(var i = 0; i < msg.length; i++){
if(fn === msg[i]){
msg.splice(i, 1); //删除特定回调数组中的回调函数
}
}
}
};
return {
listen: listen,
trigger: trigger,
remove: remove
}
})();
var fn = function(data){
console.log(data + '的推送消息:xxxxxx......');
}
Event.listen('某公众号', fn);
Event.trigger('某公众号', '2016.11.26');
Event.remove('某公众号', fn);

通过这种全局的Event对象,我们可以利用它在两个模块间实现通信 
并且两个模块互不干扰

//伪代码
module1 = function(){
Event.listen(...);
}
module2 = function(){
Event.trigger(...);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

完整的观察者对象

我们上面写的Event对象还是存在一些问题

  • 只能先“订阅”再“发布”
  • 全局对象会产生命名冲突

对于第一点,在我们订阅了一个公众号之前,它是同样会定时推送消息的 
我们订阅后,同样可以查看历史推送 
所以我们应该是实现先发布再订阅仍然可以查看历史消息

对于第二点,如果我们每个人都使用上面我们定义的函数 
所有事件都放在一个list当中 
时间长了一定会产生命名冲突 
最好的办法就是为对象增加创建命名空间的功能 
现在我们来丰满我们的事件对象 
(这里我就盗用大神写的代码了,不同的人不同库实现的方式都不同) 
这个完整版稍微了解一下就好了

var Event = (function(){
var global = this,
Event,
_default = 'default';
Event = function(){
var _listen,
_trigger,
_remove,
_slice = Array.prototype.slice,
_shift = Array.prototype.shift,
_unshift = Array.prototype.unshift,
namespaceCache = {},
_create,
find,
each = function(ary,fn){
var ret;
for(var i = 0, l = ary.length; i < l; i++){
var n = ary[i];
ret = fn.call(n,i,n);
}
return ret;
};
_listen = function(key,fn,cache){
if(!cache[key]){
cache[key] = [];
}
cache[key].push(fn);
};
_remove = function(key,cache,fn){
if(cache[key]){
if(fn){
for(var i = cache[key].length; i >= 0; i--){
if(cache[key][i] === fn){
cache[key].splice(i,1);
}
}
}else{
cache[key] = [];
}
}
};
_trigger = function(){
var cache = _shift.call(arguments),
key = _shift.call(arguments),
args = arguments,
_self = this,
ret,
stack = cache[key];
if(!stack || !stack.length){
return;
}
return each(stack,function(){
this.apply(_self,args);
});
};
_create = function(namespace){
var namespace = namespace || _default;
var cache = {},
offlineStack = [], //离线事件
ret = {
listen: function(key,fn,last){
_listen(key,fn,cache);
if(offlineStack === null){
return;
}
if(last === 'last'){
offlineStack.length && offlineStack.pop()();
}else{
each(offlineStack,function(){
this();
});
}
offlineStack = null;
},
one: function(key,fn,last){
_remove(key,cache);
this.listen(key,fn,last);
},
remove: function(key,fn){
_remove(key,cache,fn);
},
trigger: function(){
var fn,
args,
_self = this;
_unshift.call(arguments,cache);
args = arguments;
fn = function(){
return _trigger.apply(_self,args);
};
if(offlineStack){
return offlineStack.push(fn);
}
return fn();
}
};
return namespace ?
(namespaceCache[namespace] ? namespaceCache[namespace] :
namespaceCache[namespace] = ret)
: ret;
};
return {
create: _create,
one: function(key,fn,last){
var event = this.create();
event.one(key,fn,last);
},
remove: function(key,fn){
var event = this.create();
event.remove(key,fn);
},
listen: function(key,fn,last){
var event = this.create();
event.listen(key,fn,last);
},
trigger: function(){
var event = this.create();
event.trigger.apply(this,arguments);
}
};
}();
return Event;
})();
/********* 先发布后订阅 *********/
Event.trigger('click',1);
Event.listen('click',function(a){
console.log(a); //1
});
/********* 使用命名空间 *********/
Event.create('namespace1').listen('click',function(a){
console.log(a); //1
})
Event.create('namespace1').trigger('click',1);
Event.create('namespace3').listen('click',function(a){
console.log(a); //2
})
Event.create('namespace3').trigger('click',2);

总结

观察者模式有两个明显的优点

  • 时间上解耦
  • 对象间解耦

它应用广泛,但是也有缺点 
创建这个函数同样需要内存,过度使用会导致难以跟踪维护

[转] 浅析JavaScript设计模式——发布-订阅/观察者模式的更多相关文章

  1. javascript设计模式——发布订阅模式

    前面的话 发布—订阅模式又叫观察者模式,它定义对象间的一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知.在javascript开发中,一般用事件模型来替代传统的发布—订阅模 ...

  2. javascript 设计模式 -- 发布/订阅模式

    直接上代码: index.html : <!DOCTYPE html> <html lang="en"> <head> <meta cha ...

  3. JavaScript 设计模式: 发布者-订阅者模式

    JavaScript 设计模式: 发布者-订阅者模式 发布者-订阅者模式 https://github.com/Kelichao/javascript.basics/issues/22 https:/ ...

  4. 设计模式-发布订阅模式(javaScript)

    1. 前言 2. 什么是发布订阅模式 3. 发布订阅优缺点 4. 举例 4. 总结 1. 前言 发布订阅者模式是为了发布者和订阅者之间避免产生依赖关系,发布订阅者之间的订阅关系由一个中介列表来维护.发 ...

  5. js设计模式--发布订阅模式

    前言 本系列文章主要根据<JavaScript设计模式与开发实践>整理而来,其中会加入了一些自己的思考.希望对大家有所帮助. 概念 发布-订阅模式又叫观察者模式,它定义对象间的一种一对多的 ...

  6. [转]js设计模式—发布订阅模式

    发布—订阅模式又叫观察者模式,它定义对象间的一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知.在javascript开发中,一般用事件模型来替代传统的发布—订阅模式.本文将 ...

  7. js设计模式-发布/订阅模式

    一.前言 发布订阅模式,基于一个主题/事件通道,希望接收通知的对象(称为subscriber)通过自定义事件订阅主题,被激活事件的对象(称为publisher)通过发布主题事件的方式被通知. 就和用户 ...

  8. 【读书笔记】读《JavaScript设计模式》之观察者模式

    一.定义 在事件驱动的环境中,比如浏览器这种持续寻求用户关注的环境中,观察者模式(又名发布者-订阅者(publisher-subscripber)模式)是一种管理人与其任务之间的关系(确切地讲,是对象 ...

  9. javascript mqtt 发布订阅消息

    js client使用paho-mqtt,官网地址:http://www.eclipse.org/paho/,参考http://www.eclipse.org/paho/clients/js/官网给出 ...

随机推荐

  1. Linux只读账号配置【转】

    整个配置的命令如下(主要使用了:Linux bash受限的shell(RESTRICTED SHELL)) 步骤#1.创建只读shell(这步可以省略) ln -s /bin/bash /bin/rb ...

  2. node promise库bluebird

    var fs = require('fs') var Promise = require("bluebird") function file1() { return new Pro ...

  3. requests库入门09-OAUTH认证

    实际登陆中,认证用到的token会变的,不过可以在GIthub设置一个私人token. 如图,登录GIthub,然后用户下面选择Settings/Developer settings/Personal ...

  4. 【转】Linux中profile、bashrc、bash_profile之间的区别和联系

    /etc/profile:此文件为系统的每个用户设置环境信息,当用户第一次登陆时,该文件被执行.并从/etc/profile.d目录的配置文件中搜集shell的设置. 英文描述为: # /etc/pr ...

  5. struts2框架学习之第一天

    day01 Struts2概述 1 什么是框架 试想一下,人与人之间不同之处多,还是相同之处多呢?当然是相同之处多,不同之处少!人都有头,而且头都在脖子上面! 软件之间也是相同之处多,不同之处少,框架 ...

  6. HDU 3333 Turing Tree 离线 线段树/树状数组 区间求和单点修改

    题意: 给一个数列,一些询问,问你$[l,r]$之间不同的数字之和 题解: 11年多校的题,现在属于"人尽皆知傻逼题" 核心思想在于: 对于一个询问$[x,R]$ 无论$x$是什么 ...

  7. $Django 前后端之 跨域问题(同源策略) vue项目(axios跨域请求数据)

    1 跨域问题(多个域之间的数据访问) #同源策略(ip port 协议全部相同) #本站的只能请求本站域名的数据 #CORS实现(跨域资源共享) #实现CORS通信的关键是服务器.只要服务器实现了CO ...

  8. 【原创】Linux基础之Shell脚本常用命令

    #!/bin/sh 1 取脚本参数 $# 参数个数$0 当前脚本名$1 第1个参数$n 第n个参数$* 所有参数$@ 所有参数$? 上个命令的状态$$ 当前pid 2 日期 $ dateWed Mar ...

  9. 如何在同一台电脑上使用两个github账户(亲测有效)

    1 前言 由于有两个github账号,要在同一台电脑上同步代码,需要给每一个账号添加一个SSH public key,此时推送时git push origin,不知道是哪个账号的远程仓库名称,所以需要 ...

  10. linux学习之软件包安装

    本学习基于redhat系统或者centos系统 一.软件包的安装 1.rpm安装,rpm安装分为俩种,一种是直接安装xxx.rpm包,另一种是通过yum安装一系列的rpm包. #推荐使用yum安装,y ...