設計模式-觀察者模式-訂閱-發佈模式

觀察者模式,也叫訂閱-發佈模式。顧名思義,就是訂閱某些功能,而後在適當的時機發布出來,也就是執行這些功能。
訂閱:就是把幾個函數推入數組中待用;
發佈:就是把緩存在數組中的函數拿出來執行;css

var login = {};
login.eventList = {};
//將函數推入數組中保存,待用
login.listen = function(key, fn) {
    if(!this.eventList[key]) {
        this.eventList[key] = [];
    }
    this.eventList[key].push(fn);
}
login.trigger = function(key) {
    var fns = this.eventList[key];
    if(!fns || fns.length === 0) {
        return false;
    }
    for(var i=0; i<fns.length;i++) {
        fns[i]();
    }
}

//訂閱
login.listen('loginSuccess', function() {
    console.log('顯示用戶頭像');
})
login.listen('loginSuccess', function() {
    console.log('顯示消息列表');
})

//發佈
login.trigger('loginSuccess');

應用場景:
如今前端領域,SPA單頁應用已經很是廣泛了,每一個頁面,都是用ajax異步請求。ajax請求有一個比較鬧心的問題,就是層級回調。好比:
有一個頁面,須要調用三個數據接口。
第一個是login登陸接口,
第二個是根據登陸接口返回的id,調取頭像接口。
第三個是根據登陸接口返回的id,調取消息列表接口。html

通常狀況下會這麼寫:前端

$.ajax({
    url: 'http://ajax.login.com',
    dataType: 'json',
    success: function(data) {
        getAvatar(data.id);
        getMsg(data.id);
        ...
    }
})

這樣寫雖然沒有問題,但卻不容易維護。若是哪天改了需求,須要加個接口,你還得翻出這段代碼,找到success回調,再加上一個函數。加函數還算好的,有的人會直接在success回調裏繼續寫$.ajax這樣的代碼,一級一級的這麼摞着寫,這樣代碼很快就會變成一堆大便,變得不可維護。這種寫法叫作造糞模式,百分百的造出垃圾來。由於耦合性太大,接口調用都成了拴在一條繩子上的螞蚱,一扯就是一坨。
如何解耦呢?就是利用訂閱發佈模式,咱們能夠在getAvatar方法中,訂閱(listen)login接口,而一旦login接口走到success回調,咱們就發佈(trigger)一下jquery

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 === 0) {
            return false;
        }

        for(var i=0; i<fns.length; i++) {
            fns[i].apply(this, arguments);
        }
    }
};

var installEvent = function(obj) {
    //淺拷貝
    for(var i in event) {
        obj[i] = event[i];
    }
}
var login = {};
installEvent(login);

//訂閱
login.listen('loginSuccess', function() {
    console.log('顯示用戶頭像');
});
login.listen('loginSuccess', function() {
    console.log('顯示消息列表');
});
//發佈
login.trigger('loginSuccess');

如今訂閱沒有問題了,那如何取消訂閱呢?咱們再加上取消訂閱函數ajax

var event = {
    eventList: {},
    listen: function(key, fn) {
        if(!this.eventList[key]) {
            this.eventList[key] = [];
        }
        this.eventList[key].push(fn);
    },
    remove: function(key, fn) {
        var fns = this.eventList[key];
        if(!fns) {
            return false;
        }
        if(!fn) {
            //若是沒有回調,表示取消此key下的全部方法
            fns && (fns.length);
        } else {
            for(var i=0; i<fns.length; i++) {
                if(fns[i] == fn) {
                    fns.splice(i, 1);
                }
            }
        }
    },
    trigger: function() {
        var key = Array.prototype.shift.call(arguments);
        var fns = this.eventList[key];
        if(!fns || fns.length === 0) {
            reutrn false;
        }
        for(var i=0; i<fns.length; i++) {
            fns[i].apply(this, arguments);
        }
    }
};

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

var login = {};
installEvent(login);

//顯示用戶頭像
function showAvatar() {
    console.log('顯示頭像數據');
};

//顯示消息列表
function showMessage() {
    console.log('顯示消息列表');
};

//訂閱
login.listen('loginSuccess', showAvatar);
login.listen('loginSuccess', showMessage);

//發佈
login.trigger('loginSuccess');

//取消訂閱
login.remove('loginSuccess', showAvatar);

//再次發佈
login.trigger('loginSuccess');

咱們的訂閱發佈模式走到這裏,基本上已經完善了。最後咱們來看一下ajax回調問題怎麼來解決。咱們其實根本不須要在登陸的ajax回調中加拉取頭像等邏輯,而只需讓拉取頭像功能訂閱登陸接口便可,當登陸工做完成後會發布,也就是觸發緩存在數組中的函數執行。json

<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>訂閱-觀察者模式</title>
<script src="http://mockjs.com/dist/mock.js"></script>
<script src="http://cdn.bootcss.com/jquery/1.11.3/jquery.js"></script>
</head>
<body>
</body>
<script>
  Mock.mock('http://ajax.login.com', {
      'name': '@name',
      'age|1-100': 1
  });
  var event = {
      eventList: {},
      listen: function(key, fn) {
          if(!this.eventList[key]) {
              this.eventList[key] = [];
          }
          this.eventList[key].push(fn);
      },
      remove: function(key, fn) {
          var fns = this.eventList[key];
          if(!fns) {
              return false;
          }
          if(!fn) {
              fns && fns.length = 0;
          } else {
              for(var i=0; i<fns.length; i++) {
                  if(fn == fns[i]) {
                      fns.splice(i, 1);
                  }
              }
          }
      },
      trigger: function() {
          var key = Array.prototype.shift.call(arguments);
          var fns = this.eventList[key];
          if(!fns || fns.length === 0) {
              return false;
          }
          for(var i=0; i<fns.length; i++) {
              fns[i].apply(this, arguments);
          }
      }
  };

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

  var login = {};
  installEvent(obj);

  var avatar = (function() {
      login.listen('loginSucc', function() {
            avatar.setAvatar(data);
      });

      return {
          setAvatar: function(data) {
                console.log('顯示用戶' + data['name'] + '的頭像');
          }
      }
  })();

  var message = (function() {
        login.listen('loginSucc', function(data) {
          message.setMsg(data);
        });

        return {
              setMsg: function(data) {
                    setTimeout(function() {
                          console.log('顯示用戶' + data['name'] + '的消息');
                    })
              }
        }
  })();
  
  //發佈
  $.ajax({
        url: 'http://ajax.login.com',
        dataType: 'json',
        success: function(data) {
              login.trigger('loginSucc', data);
        }
  })
</script>
<html>

事實上,還有一種更廣泛意義的訂閱發佈模式。好比在一個按鈕上綁定click事件,這其實就是一個訂閱的過程;而鼠標點擊就是發佈。數組

相關文章
相關標籤/搜索