zoukankan      html  css  js  c++  java
  • JavaScript设计模式与开发实践---读书笔记(8) 发布-订阅模式

    发布-订阅模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。

    发布-订阅模式可以广泛应用于异步编程中,这是一种替代传递回调函数的方案。

    可以取代对象之间硬编码的通知机制,一个对象不用再显式地调用另外一个对象的某个接口。

    自定义事件

    1. 首先要指定好谁充当发布者;
    2. 然后给发布者添加一个缓存列表,用于存放回调函数以便通知订阅者;
    3. 最后发布消息时,发布者会遍历这个缓存列表,依次触发里面存放的订阅者回调函数。

    另外,我们还可以往回调函数里填入一些参数,订阅者可以接收这些参数。

    最简单的发布-订阅模式:

    var salesOffices = {};    //定义售楼处
        salesOffices.clientList = [];    //缓存列表,存放订阅者的回调函数
        salesOffices.listen = function(fn){        //增加订阅者
            this.clientList.push(fn);    //订阅的消息添加进缓存列表
        };
        salesOffices.trigger = function(){    //发布模式
            for(var i=0,fn;fn = this.clientList[i++]; ){    
                fn.apply(this,arguments);    //arguments是发布消息时带上的参数
            }
        };
    
        salesOffices.listen(function(price,squareMeter){//小明订阅消息
            console.log('价格='+price);
            console.log('squareMeter='+squareMeter);
        });
    
        salesOffices.listen(function(price,squareMeter){//小红订阅消息
            console.log('价格='+price);
            console.log('squareMeter='+squareMeter);
        });
    
        salesOffices.trigger(2000000,88);//输出:200万,88平方米
        salesOffices.trigger(3000000,110);//输出:300万,110平方米

    让订阅者只订阅自己感兴趣的消息:

        var salesOffices = {};    //定义售楼处
        salesOffices.clientList = [];    //缓存列表,存放订阅者的回调函数
        salesOffices.listen = function(key,fn){        
            if(!this.clientList[key]){    //如果还没有订阅过此类消息,给该类消息创建一个缓存列表
                this.clientList[key] = [];
            }
            this.clientList.push(fn);    //订阅的消息添加进缓存列表
        };
        salesOffices.trigger = function(){    //发布消息
            var key = Array.prototype.shift.call(arguments),   //取出消息类型
                fns = this.clientList[key];   //取出该消息对应的回调函数集合
    
            if(!fns || fns.length ===0 ){    //如果没有订阅该消息,则返回
                return false;
            };
            for(var i=0,fn;fn = this.clientList[i++]; ){    
                fn.apply(this,arguments);    //arguments是发布消息时附送的参数
            }
        };
    
        salesOffices.listen('squareMeter88',function(price){//小明订阅88的消息
            console.log('价格='+price);
        });
    
        salesOffices.listen('squareMeter110',function(price){//小红订阅110的消息
            console.log('价格='+price);
        });
    
        salesOffices.trigger('squareMeter88',88);
        salesOffices.trigger('squareMeter110',110);

    通用实现:

    //发布-订阅功能
        var event = {
            clientList:[],
            listen: function(key,fn){
                if(!this.clientList[key]){
                    this.clientList[key] = [];
                }
                this.clientList[key].push(fn);    //订阅的消息添加进缓存列表
            },
            trigger: function(){
                var key = Array.prototype.shift.call(arguments),
                    fns = this.clientList[key];
    
                if(!fns || fns.length ===0 ){    //如果没有绑定对应的消息
                    return false;
                }
    
                for(var i =0 ,fn;fn = fns[i++]; ){
                    fn.apply(this.arguments);    //arguments是trigger时带上的参数
                }
            }
        };
    //给所有的对象都动态安装发布-订阅功能
        var installEvent = function(obj){
            for(var i in event){
                obj[i] = event[i];
            }
        };
    
        var salesOffices = {};
        installEvent(salesOffices);
    
        salesOffices.listen('squareMeter88',function(price){    //小明订阅消息
            console.log('价格='+price);
        });
    
        salesOffices.listen('squareMeter110',function(price){    //小红订阅消息
            console.log('价格='+price);
        });
    
        salesOffices.trigger('squareMeter88',2000000);
        salesOffices.trigger('squareMeter110',3000000);

    取消订阅的事件:

    //取消订阅的事件
        event.remove = function(key,fn){
            var fns = this.clientList[key]
            if(!fns){    //如果key对应的消息没有被人订阅,则直接返回
                return false;
            }
            if(!fn){    //如果没有传入具体的回调函数,表示需要取消key对应消息的所有订阅
                fns&&(fns.length = 0);
            }else{
                for(var l = fns.length-1;l>=0;l--){    //反向遍历订阅的回调函数列表
                    var _fn = fns[l];
                    if(_fn === fn){
                        fns.splice(l,1);//删除订阅者的回调函数
                    }
                }
            }    
        };
    
        var salesOffices = {};
        var installEvent = function(obj){
            for(var i in event){
                obj[i] = event[i];
            }
        }
        installEvent(salesOffices);
    
        salesOffices.listen('squareMeter88',fn1=function(price){    //小明订阅消息
            console.log('价格='+price);
        });
    
        salesOffices.listen('squareMeter110',fn2=function(price){    //小红订阅消息
            console.log('价格='+price);
        });
    
        salesOffices.remove('squareMeter88',fn1);
        salesOffices.trigger('squareMeter110',2000000);

    全局的发布-订阅模式:

     发布-订阅模式可以用一个全局的Event对象来实现,订阅者不需要了解清楚来自哪个发布者,发布者也不知道消息会推送给哪些订阅者,Event作为一个类似“中介者”的角色,把订阅者和发布者联系起来。

    var Event = (function(){
            var clientList = {},
                listen,
                trigger,
                remove;
    
            listen = function(key,fn){
                if(!clientList[key]){
                    clientList[key] = [];
                }
                clientList[key].push(fn);
            };
    
            trigger = function(){
                var key = Array.prototype.shift.call(arguments),
                    fns = clientList[key];
                    if(!fns||fns.length===0){
                        return false;
                    }
                    for(var i=0,fn;fn=fns[i++]; ){
                        fn.apply(this,arguments);
                    }
            };
    
            remove = function(key,fn){
                var fns = clientList[key];
                if(!fns){
                    return false;
                }
                if(!fn){
                    fns&&(fns.length = 0);
                }else{
                    for(var l=fns.length-1;l>=0;l--){
                        var _fn = fns[l];
                        if(_fn ===fn){
                            fns.splice(l,1);
                        }
                    }
                }
            };
    
            return {
                listen:listen,
                trigger:trigger,
                remove:remove
            }
        })();
    
        Event.listen('squareMeter88',function(price){
            console.log('价格='+price);
        });
    
        Event.trigger('squareMeter88',2000000);

    模块间通信:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>模块间通信</title>
    </head>
    <body>
        <button id="count">点我</button>
        <div id="show"></div>
    </body>
    </html>
    <script>
        var a = (function(){
            var count = 0;
            var button = document.getElementById('count');
            button.click = function(){
                Event.trigger('add',count++);
            }
        })();
    
        var b = (function(){
            var div = document.getElementById('show');
            Event.listen('add',function(count){
                div.innerHTML = count;
            });
        })();
    </script>

    必须先订阅再发布吗?

    在某些情况下,我们需要先将这条消息保存下来,等到有对象来订阅它的时候,再重新把消息发布给订阅者。

    全局事件的命名冲突:

    给Event对象提供创建命名空间的功能。

    在JavaScript中,我们用注册回调函数的形式来代替传统的发布-订阅模式,显得更加优雅和简单。

    发布-订阅模式的优点非常明显,一位时间上的解耦,二为对象上的解耦。

  • 相关阅读:
    c# where(泛型类型约束)
    jQuery自定义插件
    jQuery插件定义
    SQL中merge into用法
    .net framework 4.5安装失败
    jQuery操作Form表单元素
    在WebAPI使用Session
    大数据量数据库设计与优化方案(SQL优化)
    修改NUGET包默认存放位置
    C#知识体系(一) 常用的LInq 与lambda表达式
  • 原文地址:https://www.cnblogs.com/6489c/p/5940008.html
Copyright © 2011-2022 走看看