Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

设计模式——发布-订阅模式 #33

Open
hoperyy opened this issue Jun 26, 2017 · 0 comments
Open

设计模式——发布-订阅模式 #33

hoperyy opened this issue Jun 26, 2017 · 0 comments

Comments

@hoperyy
Copy link
Owner

hoperyy commented Jun 26, 2017

  • 自定义事件

    var obj = {};
    
    obj.eventList = [];
    
    obj.listen = function(fn) {
      obj.eventList.push(fn);
    };
    
    obj.trigger = function() {
      for(var i = 0, fn; fn = this.eventList[i++];) {
        fn.apply(this, arguments);
      }
    };
    
    

    增加标示 key

    var obj = {};
    
    obj.eventList = [];
    
    obj.listen = function(key, fn) {
    
      if (!this.eventList[key]) {
        this.eventList[key] = [];
      }
    
      obj.eventList[key].push(fn);
    };
    
    obj.trigger = function() {
      var key = Array.prototype.shift.call(arguments);
      var fns = this.eventList[key];
    
      if (!fns || !fns.length) {
        return false;
      }
    
      for(var i = 0, fn; fn = fns[i++];) {
        fn.apply(this, arguments);
      }
    };
    
    
  • 发布-订阅的通用实现

    var event = {
      eventList: [],
    
      listen: function(key, fn) {
      
        if (!this.eventList[key]) {
          this.eventList[key] = [];
        }
    
        this.eventList[key].push(fn);
      },
    
      trigger: function() {
        var key = Array.prototype.shift.call(arguments);
        var fns = this.eventList[key];
    
        if (!fns || !fns.length) {
          return false;
        }
    
        for(var i = 0, fn; fn = fns[i++];) {
          fn.apply(this, arguments);
        }
      }
    };
    

    再加一个包装函数

    var installEvent = function(obj) {
      for(var i in event) {
        obj[i] = event[i];
      }
    };
    

    取消订阅的事件:增加一个 remove 函数

    event.remove = function(key, fn) {
      var fns = this.eventList[key];
    
      // 如果 key 对应的事件没有被订阅,则直接返回
      if (!fns) {
        return false;
      }
      
      // 如果没有指定某个特定的函数,则全部移除
      if (!fn) {
        this.eventList[key].length = 0; // 数组的 length 属性直接置为零
      } else {
        // 倒序删除
        for (var i = fns.length - 1; i >= 0; i--) {
          var _fn = fns[i];
    
          if (_fn === fn) {
            fns.splice(i, 1);
          }
        }
      }
    };
    
  • 一次实际应用:登录框

    异步登录后,一般会触发页面上多个模块的动作,但在后面的需求中,又可能不知道哪些模块会增减。这种情况下可以用发布-订阅模式解决。

    原始写法:

    loginSuccess(function() {
      header.init();
      nav.init();
      footer.init();
      sidebar.init();
    });
    

    这种情况下,万一后面新增了了一个模块,比如购物车 shopBar 也依赖登录状态的改变,那么就要在原来的代码里增加 shopBar.init() 的逻辑了。

    然而,其实登录只需要发布登录成功的消息,其他模块订阅该消息即可,登录模块不需要关心业务逻辑。

    改造如下:

    // 发布
    $.ajax('xxx', function(data) {
      login.trigger('loginSuccess', data);
    });
    
    // 订阅
    var header = (function() {
      login.listen('loginSuccess', function(data) {
        header.init(data);
      });
    
      return {
        init: function() {
          console.log('header init');
        }
      };
    })();
    
    var nav = (function() {
      login.listen('loginSuccess', function(data) {
        nav.init(data);
      });
    
      return {
        init: function() {
          console.log('nav init');
        }
      };
    })();
    
  • 必须先订阅后发布吗?

    当然不是,可以先发布后订阅。

    当发布的时候,如果没有函数订阅,可以先缓存到一个函数里,有函数订阅的时候直接触发执行,而且只执行一次。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant