[转] 浅析JavaScript设计模式——发布-订阅/观察者模式

前一段时间一直在写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);

总结

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

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

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

原文地址:https://www.cnblogs.com/chris-oil/p/8727161.html

时间: 2024-10-09 15:27:07

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

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

直接上代码: index.html : <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>设计模式</title> </head> <body> <div id="box"> <div>{{message}}</div> <

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

一.定义 在事件驱动的环境中,比如浏览器这种持续寻求用户关注的环境中,观察者模式(又名发布者-订阅者(publisher-subscripber)模式)是一种管理人与其任务之间的关系(确切地讲,是对象及其行为和状态之间的关系)的得力工具.用JavaScript的话来说,这种模式的实质就是你可以对程序中某个对象的状态进行观察,并且在其发生改变时能够得到通知. 二.例子 我们需要一个发布者的构造函数,它为该实例定义了一个类型为数组的属性,用来保存订阅者的引用. function Publisher(

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

1. 前言 2. 什么是发布订阅模式 3. 发布订阅优缺点 4. 举例 4. 总结 1. 前言 发布订阅者模式是为了发布者和订阅者之间避免产生依赖关系,发布订阅者之间的订阅关系由一个中介列表来维护.发布者只需做好发布功能,至于订阅者是谁,订阅者做了什么事情,发布者是无需关心的 2. 什么是发布订阅模式 发布订阅:是一种消息范式,消息的发送者(称为发布者)不会将消息直接发送给特定的接收者(称为订阅者).而是将发布的消息分为不同的类别,无需了解哪些订阅者(如果有的话)可能存在.同样的,订阅者可以表达

设计模式 - 发布-订阅者模式

1.发布-订阅者 设计模式 定义 定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知 观察者模式和发布订阅模式区别 观察者模式是由具体目标(发布者/被观察者)调度的,而发布/订阅模式是由独立的调度中心进行调度,所以观察者模式的订阅者与发布者之间是存在依赖的,而发布/订阅模式则不会:可以说发布订阅模式是观察者模式进一步解耦,在实际中被大量运用的一种模式 ** 观察者模式 ** 1.定义/解析 目标和观察者是基类,目标提供维护观察者的一系列方法,观察者提供更

设计模式发布订阅

参考 http://blog.csdn.net/calmreason/article/details/50895191 主要是发布者存储订阅者的指针 发布时依次NOTIFY 发送信息 订阅者在订阅时将自己加入到发布者的容器中 使用C++11 的智能指针 需要注意智能指针的 weak shared的转换 以及订阅者析构后的对应操作 代码 // MyDesignPattern.cpp: 定义控制台应用程序的入口点. // #include "stdafx.h" #include <i

Javascript设计模式之发布-订阅模式

简介 发布-订阅模式又叫做观察者模式,他定义了一种一对多的依赖关系,即当一个对象的状态发生改变的时候,所有依赖他的对象都会得到通知. 回忆曾经 作为一名前端开发人员,给DOM节点绑定事件可是再频繁不过的事情.比如如下代码 document.body.addEventListener('click',function () { alert(2333); },false); document.body.click();//模拟点击事件 这里我们订阅了document.body的click事件,当bo

观察者模式 vs 发布-订阅模式

我曾经在面试中被问道,_“观察者模式和发布订阅模式的有什么区别?” _我迅速回忆起“Head First设计模式”那本书: 发布 + 订阅 = 观察者模式 “我知道了,我知道了,别想骗我” 我微笑着回答:“没有区别,它们是一样的.” 但是面试官笑了,“不,它们不一样.” 我当时的表情: 所以是我错了吗? 之后我回到家打开google查找答案.这篇文章就是我google后的总结. 在深入探讨区别之前,我们先来讨论下“观察者模式”和“发布订阅模式”. 观察者设计模式: 我认为大多数人都会同意观察者模

JavaScript设计模式与开发实践---读书笔记(8) 发布-订阅模式

发布-订阅模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知. 发布-订阅模式可以广泛应用于异步编程中,这是一种替代传递回调函数的方案. 可以取代对象之间硬编码的通知机制,一个对象不用再显式地调用另外一个对象的某个接口. 自定义事件 首先要指定好谁充当发布者: 然后给发布者添加一个缓存列表,用于存放回调函数以便通知订阅者: 最后发布消息时,发布者会遍历这个缓存列表,依次触发里面存放的订阅者回调函数. 另外,我们还可以往回调函数里填入

理解《JavaScript设计模式与开发应用》发布-订阅模式的最终版代码

最近拜读了曾探所著的<JavaScript设计模式与开发应用>一书,在读到发布-订阅模式一章时,作者不仅给出了基本模式的通用版本的发布-订阅模式的代码,最后还做出了扩展,给该模式增加了离线空间功能和命名空间功能,以达到先发布再订阅的功能和防止名称冲突的效果.但是令人感到遗憾的是最终代码并没有给出足够的注释.这让像我一样的小白就感到非常的困惑,于是我将这份最终代码仔细研究了一下,并给出了自己的一些理解,鉴于能力有限,文中观点可能并不完全正确,望看到的大大们不吝赐教,谢谢! 下面是添加了个人注释的