zoukankan      html  css  js  c++  java
  • [转] 浅析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);

    总结

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

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

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

  • 相关阅读:
    ztree——Cannot read property 'init' of undefined解决方案
    vue——手写swiper子组件,pagination不显示、轮播无效问题解决
    angularJS——数据更新了但是view(视图)层却未更新问题及解决方法
    vue——router.js动态注册组件
    js——ev || window.event,event.srcElement || event.target
    vue——keepAlive第一次无效问题及解决方法
    vue——列表页进详情页,第一次很慢,第二次就很快问题及解决方法
    vue——按需引入elementUI(以时间选择器为例)
    vue——预先指定高度,进行懒加载
    sql 分页查询
  • 原文地址:https://www.cnblogs.com/chris-oil/p/8727161.html
Copyright © 2011-2022 走看看