浅析JavaScript设计模式——发布-订阅/观察者模式
来源:互联网 发布:php 配置artisan 编辑:程序博客网 时间:2024/05/29 14:07
观察者模式
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知
前一段时间一直在写CSS3的文章
一直都没写设计模式
今天来写写大名鼎鼎观察者模式
先画张图
观察者模式的理解
我觉得还是发布-订阅模式的叫法更容易我们理解
(不过也有的书上认为它们是两种模式……)
这就类似我们在微信平台订阅了公众号
当它有新的文章发表后,就会推送给我们所有订阅的人
我们可以看到例子中这种模式的优点
- 我们作为订阅者不必每次都去查看这个公众号有没有新文章发布,
公众号作为发布者会在合适时间通知我们 - 我们与公众号之间不再强耦合在一起。公众号不关心谁订阅了它,
不管你是男是女还是宠物狗,它只需要定时向所有订阅者发布消息即可
很简单的道理,过年的时候,群发祝福短信一定要比挨个发短信方便的多
通过上面的例子映射出我们观察者模式的优点
- 可以广泛应用于异步编程,它可以代替我们传统的回调函数
我们不需要关注对象在异步执行阶段的内部状态,我们只关心事件完成的时间点 - 取代对象之间硬编码通知机制,一个对象不必显式调用另一个对象的接口,而是松耦合的联系在一起
虽然不知道彼此的细节,但不影响相互通信。更重要的是,其中一个对象改变不会影响另一个对象
可能看完这些一脸懵逼,不过没关系
下面我们会深入体会它的优点
自定义事件
其实观察者模式我们都曾使用过,就是我们熟悉的事件
但是内置的事件很多时候不能满足我们的要求
所以我们需要自定义事件
现在我们想实现这样的功能
定义一个事件对象,它有以下功能
- 监听事件(订阅公众号)
- 触发事件(公众号发布)
- 移除事件(取订公众号)
当然我们不可能只订阅一个公众号,可能会有很多
所以我们要针对不同的事件设置不同的”键”
这样我们储存事件的结构应该是这样的
//伪代码Event = { name1: [回调函数1,回调函数2,...], name2: [回调函数1,回调函数2,...], name3: [回调函数1,回调函数2,...],}
代码如下
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(...);}
完整的观察者对象
我们上面写的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设计模式——发布-订阅/观察者模式
- 观察者(发布-订阅)模式浅析
- 浅谈JavaScript设计模式——观察者模式(发布订阅模式)
- Javascript设计模式-07-观察者模式(发布订阅模式)
- java设计模式-观察者(发布-订阅)模式
- 设计模式——订阅发布模式
- 观察者模式--发布/订阅模式
- 十六 设计模式之观察者模式(发布订阅模式)
- 监听器设计模式(观察者模式、订阅发布模式)
- 设计模式之观察者模式(发布-订阅模式)
- [设计模式] 观察者模式(发布-订阅/Publish-subscribe模式)
- 我理解的设计模式:观察者模式(订阅发布模式)
- 设计模式二:观察者模式(发布订阅模式)
- javascript设计模式-(发布-订阅模式)
- JavaScript设计模式-发布订阅模式
- javascript 设计模式 发布订阅模式
- java笔记--设计模式之观察者(订阅/发布)模式
- 设计模式(三)观察者模式Observer(发布订阅)
- JAVA中<<和>>的理解
- Docker学习笔记
- imooc js进阶篇 3-8编程练习
- 玲珑杯-1058-dfs
- 【python gensim使用】word2vec词向量处理英文语料
- 浅析JavaScript设计模式——发布-订阅/观察者模式
- Python虚拟环境使用
- Longest Palindromic Substring(最长回文)
- 凸集的极锥(polars)
- Unity3d下载大型文件并显示进度
- PAT_乙级 1010 一元多项式求导
- #sicily#1004. Forest
- iOS block (block中修改外部值并作为返回值)
- 欢迎使用CSDN-markdown编辑器