zoukankan      html  css  js  c++  java
  • 设计模式之观察者模式(发布订阅模式)

    观察者模式:

    这是一种创建松散耦合代码的技术。它定义对象间 一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。由主体和观察者组成,主体负责发布事件,同时观察者通过订阅这些事件来观察该主体。主体并不知道观察者的任何事情,观察者知道主体并能注册事件的回调函数。可以广泛应用于异步编程,它可以代替我们传统的回调函数

     观察者模式的优点:

    我们不需要关注对象在异步执行阶段的内部状态,我们只关心事件完成的时间点

    取代对象之间硬编码通知机制,一个对象不必显式调用另一个对象的接口,而是松耦合的联系在一起

    虽然不知道彼此的细节,但不影响相互通信。更重要的是,其中一个对象改变不会影响另一个对象

    缺点 :

    创建这个函数同样需要内存,过度使用会导致难以跟踪维护

    例子:

      假如我们正在开发一个商城网站,网站里有header头部、nav导航、消息列表、购物车等模块。这几个模块的渲染有一个共同的前提条件,就是必须先用ajax异步请求获取用户的登录信息。这是很正常的,比如用户的名字和头像要显示在header模块里,而这两个字段都来自用户登录后返回的信息。这个时候,我们就可以把这几个模块的渲染事件都放到一个数组里面,然后待登录成功之后再遍历这个数组并且调用每一个方法。

    基本模式:

    function EventTarget(){     

        this.handlers = {};

    }

    EventTarget.prototype = {     

        constructor: EventTarget,

        addHandler: function(type, handler){

             if (typeof this.handlers[type] == "undefined"){
                  this.handlers[type] = [];

             }

             this.handlers[type].push(handler);

         },

        fire: function(event){

             if (!event.target){

                 event.target = this;

             }

             if (this.handlers[event.type] instanceof Array){

                 var handlers = this.handlers[event.type];

                 for (var i=0, len=handlers.length; i < len; i++){
                     handlers[i](event);

                }

             }

         },

         removeHandler: function(type, handler){

            if (this.handlers[type] instanceof Array){

                var handlers = this.handlers[type];

                for (var i=0, len=handlers.length; i < len; i++){

                    if (handlers[i] === handler){

                        break;

                     }

                 }

                 handlers.splice(i, 1);

              }

          }

    };

    以发布订阅的角度来思考:

    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);

    题目:

    // 测试1

    Event.on('test', function (result) {

        console.log(result);

    });

    Event.on('test', function () {

        console.log('test');

    });

    Event.emit('test', 'hello world'); // 输出 'hello world' 和 'test'

    意思就是,定义一个叫'test'类型的事件集,并且注册了两个test事件。然后调用test事件集里面的全部方法。在这里on方法等价于addHandle方法,emit方法等价于fire方法。其中第一个参数就是事件类型,第二个参数就是要传进函数的参数。

    var Event = {

        // 通过on接口监听事件eventName

        // 如果事件eventName被触发,则执行callback回调函数

        on: function (eventName, callback) {

            //我的代码

            if(!this.handles){

                 this.handles={};    

            }      

           if(!this.handles[eventName]){

                this.handles[eventName]=[];

           }

           this.handles[eventName].push(callback);

        },

        // 触发事件 eventName

        emit: function (eventName) {

            //你的代码

           if(this.handles[arguments[0]]){

               for(var i=0;i<this.handles[arguments[0]].length;i++){

                   this.handles[arguments[0]][i](arguments[1]);

               }

           }

        }

    };

    var person1 = {};var person2 = {};

    Object.assign(person1, Event);

    Object.assign(person2, Event);

    person1.on('call1', function () {

        console.log('person1');

    });

    person2.on('call2', function () {

        console.log('person2');

    });

    person1.emit('call1'); // 输出 'person1'

    person1.emit('call2'); // 没有输出

    person2.emit('call1'); // 没有输出

    person2.emit('call2'); // 输出 'person2'

    大概意思就是为两个不同person注册自定义事件,并且两个person之间是互相独立的。

    解释一下,Object.assign(person1, Event);

    这个是ES6的新对象方法,用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。

    意思是将Event里面的可枚举的对象和方法放到person1里面。

    也就是说,如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用。由于进行测试一的时候调用了on方法,所以event里面已经有了handles这个可枚举的属性。然后再分别合并到两个person里面的话,两个person对象里面的handles都只是一个引用。所以就互相影响了

    所以,我们必须将handles这个属性定义为不可枚举的,然后在person调用on方法的时候再分别产生handles这个对象。

    也就是说正确的做法应该是:

    var Event = {

        // 通过on接口监听事件eventName

        // 如果事件eventName被触发,则执行callback回调函数

        on: function (eventName, callback) {

            //你的代码

            if(!this.handles){

                //this.handles={};

                Object.defineProperty(this, "handles", {

                    value: {},

                    enumerable: false,

                    configurable: true,

                    writable: true

                })

            }

           

           if(!this.handles[eventName]){

                this.handles[eventName]=[];

           }

           this.handles[eventName].push(callback);

        },

        // 触发事件 eventName

        emit: function (eventName) {

            //你的代码

           if(this.handles[arguments[0]]){

               for(var i=0;i<this.handles[arguments[0]].length;i++){

                   this.handles[arguments[0]][i](arguments[1]);

               }

           }

        }

    };

     

  • 相关阅读:
    JavaEE三层架构
    请求重定向
    响应的中文乱码问题
    Apache的ServerAlias的作用
    bootstrap 常用class
    linux 退出当前命令的编辑
    硬链接和软链接
    ALTER TABLE causes auto_increment resequencing, resulting in duplicate entry ’1′ for key ‘PRIMARY’
    ie浏览器许多图片放在一起会有间隙
    Could not initialize class utils.JdbcUtils
  • 原文地址:https://www.cnblogs.com/zxyCC/p/10149768.html
Copyright © 2011-2022 走看看