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中,我们用注册回调函数的形式来代替传统的发布-订阅模式,显得更加优雅和简单。

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

时间: 2024-12-26 14:30:09

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

JavaScript 设计模式与开发实践读书笔记 http://www.open-open.com/lib/view/open1469154727495.html

JavaScript 设计模式与开发实践读书笔记 最近利用碎片时间在 Kindle 上面阅读<JavaScript 设计模式与开发实践读书>这本书,刚开始阅读前两章内容,和大家分享下我觉得可以在项目中用的上的一些笔记. 我的 github 项目会不定时更新,有需要的同学可以移步到我的 github 中去查看源码: https://github.com/lichenbuliren/design-mode-notes 1.currying 函数柯里化 currying 又称 部分求值 .一个 cu

JavaScript设计模式与开发实践——读书笔记1.高阶函数(下)

上部分主要介绍高阶函数的常见形式,本部分将着重介绍高阶函数的高级应用. 1.currying currying指的是函数柯里化,又称部分求值.一个currying的函数会先接受一些参数,但不立即求值,而是继续返回给另一个函数,通过闭包存储起来.等到函数被真正需求要求值的时候,将之前传入的参数统一起来求值.例如,我们要计算一个月的开销,我们并不需要计算每天具体花了多少,而是需要计算月底总共花掉多少,也就是说,实际上我们只需要在月底计算一次.所以每个月的前29天,我们都只需要保存好当天的开销,到30

JavaScript设计模式与开发实践---读书笔记(1)

前言 设计模式的定义是:在面向对象软件设计过程中针对特定问题的简洁而优雅的解决方案. 从某些角度来看,设计模式确实有可能带来代码量的增加,或许会把系统的逻辑搞的更复杂.但软件开发的成本并非全部在开发阶段,设计模式的作用是让人们写出可复用和可维护性高的程序. 所有设计模式的实现都遵循一条原则,即“找出程序中变化的地方,并将变化封装起来”. 不变和稳定的部分是非常容易复用的. 分辨模式的关键是意图而不是结构 模式只有放在具体的环境下才有意义,辨别模式的关键是这个模式出现的场景,以及为我们解决的问题.

JavaScript设计模式与开发实践---读书笔记(7) 迭代器模式

迭代器模式是指提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示. JavaScript中的Array.prototype.foreach. 1.JQuery中的迭代器 $.each函数 2.自己实现一个each函数 var each = function(ary,callback){ for(var i=0,l=ary.length;i<l;i++){ callback.call(ary[i],i,ary[i]);//把下标和元素当作参数传给callback函数 }

JavaScript设计模式与开发实践-读书笔记(3)闭包和高阶函数

闭包(closure) 闭包的形成与变量的作用域以及变量的生存周期密切相关. 变量的作用域,就是指变量的有效范围. 全局变量和局部变量. 在JavaScript中,函数可以用来创造函数作用域. 变量的生存周期,全局变量的生命周期是永久的,除非我们主动销毁这个全局变量. 对于在函数体内用var关键字声明的局部变量来说,当退出函数时,这些局部变量即失去了它们的价值,它们都会随着函数调用的结束而被销毁. 利用闭包我们可以完成许多奇妙的工作. 闭包的作用: 1.封转变量 闭包可以帮助我们把一些不需要暴露

Javascript设计模式与开发实践读书笔记(1-3章)

第一章 面向对象的Javascript 1.1 多态在面向对象设计中的应用   多态最根本好处在于,你不必询问对象“你是什么类型”而后根据得到的答案调用对象的某个行为--你只管调用行为就好,剩下的一切多态会搞定 换句话说就是:多态的最根本作用就是把过程化的条件分支语句转化为对象的多态性,从而消除这些条件分支语句 例子:假设有一个地图应用,每个地图API提供商都提供了show方法,负责在页面上显示地图,首先我们用一些分支条件语句来实现一个调用方法renderMap 此时一旦需要增加搜搜地图的应用,

JavaScript设计模式与开发实践---读书笔记(5) 策略模式

策略模式的定义是:定义一系列的算法,把它们一个个封转起来,并且使它们可以相互替换. JavaScript版本的策略模式: 奖金系统: var strategies = { "S": function(salary){ return salary*4; }, "A": function(salary){ return salary*3; }, "B": function(salary){ return salary*2; } }; var calc

JavaScript设计模式与开发实践---读书笔记(9) 命令模式

命令模式的用途: 命令模式是最简单和优雅的模式之一,命令模式中的命令(command)指的是一个执行某些特定事情的指令. 命令模式最常见的应用场景是:有时候需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是什么.此时希望用一种松耦合的方式来设计程序,使得请求发送者和请求接收者能够消除彼此之间的耦合关系. 命令模式的例子-菜单程序: <!DOCTYPE html> <html lang="en"> <head> <met

JavaScript设计模式与开发实践---读书笔记(10) 组合模式

组合模式就是用小的子对象来构建更大的对象,而这些小的子对象也许是由更小的"孙对象"构成的. 组合模式将对象组合成树形结构,以表示"部分-整体"的层次结构. 抽象类在组合模式中的作用: 组合模式最大的优点在于可以一致地对待组合对象和基本对象.这种透明性带来的便利,在静态类型语言中体现的尤为明显. JavaScript中实现组合模式的难点在于要保证组合对象和叶对象拥有同样的方法,这通常需要用鸭子类型的思想对它们进行接口检查. 透明性带来的安全问题: 组合模式的例子-扫描