天天看點

【單頁應用之通信機制】view之間應該如何通信

前言

在單頁應用中,view與view之間的通信機制一直是一個重點,因為單頁應用的所有操作以及狀态管理全部發生在一個頁面上

沒有很好的組織的話很容易就亂了,就算表面上看起來沒有問題,事實上會有各種隐憂,各種坑等着你去跳

最初就沒有一定理論上的支撐,極有可能是這麼一種情況:

① 需求下來了,搞一個demo做交待

② 發現基本滿足很滿意,于是直接在demo中做調整

上面的做法本身沒有什麼問題,問題發生在後期

③ 在demo調整後應用到了實際業務中,發現很多地方有問題,于是見一個坑解決一個坑

④ 到最後感覺整個架構零零散散,有很多if代碼,有很多代碼不太懂意思,但是一旦移除就報錯

這個時候我們就想到了重構,重構過程中就會發現最初的設計,或者說整個架構的基礎有問題,于是就提出推翻重來

若是時間上允許,還可以,但是往往重構過程中,會多一些不按套路出牌的同學,将API接口給換了,這一換所有的業務系統全部崩潰

是以說,新的架構會對業務線造成壓力,會提高測試與編碼成本,于是就回到了我們上篇部落格的問題

【UI插件】簡單的月曆插件(下)—— 學習MVC思想

一些同學認為,以這種方式寫UI元件過于麻煩,但是我們實際的場景是這樣的

我們所有的UI 元件可能會由一個UIAbstractView繼承而來,這樣的繼承的好處是:

① 我們每個UI元件都會遵循一個事件的流程做編寫,比如:

onCreate->preShow->show->afterShow->onHide->destroy (簡單說明即可)

于是我們想在每一個元件顯示前做一點操作的話,我們可以統一寫到AbstractView中去(事實上我們應該寫到businessView中)

② 在AbstractView中我們可以維護一個共用的閉包環境,這個閉包環境被各個UI元件共享,于是UI與UI之間的通信就變成了執行個體的操作而不是dom操作

當然,事實上通過DOM的操作,選擇器,id的什麼方式可能一樣可以實作相同的功能,但是正如上面所言,這種方式會有隐憂

事實上是對UI元件編寫的一種限制,沒有限制的元件做起來當然簡單

但是有了限制的元件的狀态處理才能被統一化,因為

單頁應用的記憶體清理、狀态管理才是真正的難點

PS:此文僅代表個人淺薄想法,有問題請指出

消息通信機制

其實所謂消息通信,不過是一種釋出訂閱的關系,又名觀察者;觀察者有着一對多的關系

多個對象觀察同一個主體對象,若是主體對象發生變化便會通知所有觀察者變化,事實上觀察者本身又可以變成主體對象,是以多對多的關系偶爾不可避免

還有一些時候觀察者也可能變成自己,自己的某些狀态會被觀察

其實前面扯那麼多有的沒的不如來一個代碼,在Backbone中有一段代碼簡單實作了這個邏輯

 1 var Events = Backbone.Events = {

 2   on: function (name, callback, context) {

 3     if (!eventsApi(this, 'on', name, [callback, context]) || !callback) return this;

 4     this._events || (this._events = {});

 5     var events = this._events[name] || (this._events[name] = []);

 6     events.push({ callback: callback, context: context, ctx: context || this });

 7     return this;

 8   },

 9 

10   off: function (name, callback, context) {

11     var retain, ev, events, names, i, l, j, k;

12     if (!this._events || !eventsApi(this, 'off', name, [callback, context])) return this;

13     if (!name && !callback && !context) {

14       this._events = {};

15       return this;

16     }

17 

18     names = name ? [name] : _.keys(this._events);

19     for (i = 0, l = names.length; i < l; i++) {

20       name = names[i];

21       if (events = this._events[name]) {

22         this._events[name] = retain = [];

23         if (callback || context) {

24           for (j = 0, k = events.length; j < k; j++) {

25             ev = events[j];

26             if ((callback && callback !== ev.callback && callback !== ev.callback._callback) ||

27               (context && context !== ev.context)) {

28               retain.push(ev);

29             }

30           }

31         }

32         if (!retain.length) delete this._events[name];

33       }

34     }

35 

36     return this;

37   },

38 

39   trigger: function (name) {

40     if (!this._events) return this;

41     var args = slice.call(arguments, 1);

42     if (!eventsApi(this, 'trigger', name, args)) return this;

43     var events = this._events[name];

44     var allEvents = this._events.all;

45     if (events) triggerEvents(events, args);

46     if (allEvents) triggerEvents(allEvents, arguments);

47     return this;

48   },

49 };

這是一段簡單的邏輯,也許他的主幹還不全,我們這裡若是做一個簡單的實作的話就會變成這個樣子:

 1 var Events = {};

 2 Events.__events__ = {};

 3 

 4 Events.addEvent = function (type, handler) {

 5   if (!type || !handler) {

 6     throw "addEvent Parameter is not complete!";

 7   }

 8   var handlers = Events.__events__[type] || [];

 9   handlers.push(handler);

10   Events.__events__[type] = handlers;

11 };

12 

13 Events.removeEvent = function (type, handler) {

14   if (!type) {

15     throw "removeEvent parameters must be at least specify the type!";

16   }

17   var handlers = Events.__events__[type], index;

18   if (!handlers) return;

19   if (handler) {

20     for (var i = Math.max(handlers.length - 1, 0); i >= 0; i--) {

21       if (handlers[i] === handler) handlers.splice(i, 1);

22     }

23   } else {

24     delete handlers[type];

25   }

26 };

27 

28 Events.trigger = function (type, args, scope) {

29   var handlers = Events.__events__[type];

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

31     typeof handlers[i] === 'function' && handlers[i].apply(scope || this, args);

32   }

33 };

整個程式邏輯如下:

① 建立一個events對象作為消息存放點

② 使用on放events中存放一個個事件句柄

③ 在滿足一定條件的情況下,觸發相關的事件集合

IScroll中的消息機制

簡單而言,以IScroll為例,他在構造函數中定義了預設的屬性:

this._events = {};

然後提供了最簡單的注冊、觸發接口

 1 on: function (type, fn) {

 2   if (!this._events[type]) {

 3     this._events[type] = [];

 4   }

 5 

 6   this._events[type].push(fn);

 7 },

 8 

 9 _execEvent: function (type) {

10   if (!this._events[type]) {

11     return;

12   }

13 

14   var i = 0,

15         l = this._events[type].length;

16 

17   if (!l) {

18     return;

19   }

20 

21   for (; i < l; i++) {

22     this._events[type][i].call(this);

23   }

24 },

因為IScroll中涉及到了自身與滾動條之間的通信,是以是個很好的例子,我們看看IScroll的使用:

他對自身展開了監聽,若是發生以下事件便會觸發響應方法

 1 _initIndicator: function () {

 2   //滾動條

 3   var el = createDefaultScrollbar();

 4   this.wrapper.appendChild(el);

 5   this.indicator = new Indicator(this, { el: el });

 6 

 7   this.on('scrollEnd', function () {

 8     this.indicator.fade();

 9   });

10 

11   var scope = this;

12   this.on('scrollCancel', function () {

13     scope.indicator.fade();

14   });

15 

16   this.on('scrollStart', function () {

17     scope.indicator.fade(1);

18   });

19 

20   this.on('beforeScrollStart', function () {

21     scope.indicator.fade(1, true);

22   });

23 

24   this.on('refresh', function () {

25     scope.indicator.refresh();

26   });

28 },

比如在每次拖動結束的時候,皆會抛一個事件出來

that._execEvent('scrollEnd');

他隻負責抛出事件,然後具體執行的邏輯其實早就寫好了,他不必關注起做了什麼,因為那個不是他需要關注的

再說回頭,IScroll的事件還可以被使用者注冊,于是使用者便可以在各個事件點封裝自己想要的邏輯

比如IScroll每次移動的結果都會是一個步長,便可以在scrollEnd觸發自己的邏輯,但是由于iScroll最後的移動值為一個局部變量,是以這裡可能需要将其中的newY定制于this上

實作消息中心

如IScroll的消息機制隻會用于自身,如Backbone的Model、View層各自維護着自己的消息中心,在一個單頁架構中,此消息樞紐事實上可以隻有一個

比如頁面标簽的View可以是一個消息群組

UI元件可以是一個消息群組

Model層也可以是一個消息群組

......

是以這個統一的消息中心,事實上我們一個架構可以提供一個單例,讓各個系統去使用

  7 Dalmatian = {};

  8 

  9 Dalmatian.MessageCenter = _.inherit({

 10   initialize: function () {

 11     //架構所有的消息皆存于此

 12     /* 

 13     {

 14     view: {key1: [], key2: []},

 15     ui: {key1: [], key2: []},

 16     model: {key1: [], key2: []}

 17     other: {......}

 18     }

 19     */

 20     this.msgGroup = {};

 21   },

 22 

 23   _verify: function (options) {

 24     if (!_.property('namespace')(options)) throw Error('必須知道該消息的命名空間');

 25     if (!_.property('id')(options)) throw Error('該消息必須具備key值');

 26     if (!_.property('handler')(options) && _.isFunction(options.handler)) throw Error('該消息必須具備事件句柄');

 27   },

 28 

 29   //注冊時需要提供namespace、key、事件句柄

 30   //這裡可以考慮提供一個message類

 31   register: function (namespace, id, handler) {

 32     var message = {};

 33 

 34     if (_.isObject(namespace)) {

 35       message = namespace;

 36     } else {

 37       message.namespace = namespace;

 38       message.id = id;

 39       message.handler = handler;

 40 

 41     }

 42 

 43     this._verify(message);

 44 

 45     if (!this.msgGroup[message.namespace]) this.msgGroup[message.namespace] = {};

 46     if (!this.msgGroup[message.namespace][message.id]) this.msgGroup[message.namespace][message.id] = [];

 47     this.msgGroup[message.namespace][message.id].push(message.handler);

 48   },

 49 

 50   //取消時候有所不同

 51   //0 清理所有

 52   //1 清理整個命名空間的事件

 53   //2 清理一個命名空間中的一個

 54   //3 清理到具體執行個體上

 55   unregister: function (namespace, id, handler) {

 56     var removeArr = [

 57       'clearMessageGroup',

 58       'clearNamespace',

 59       'clearObservers',

 60       'removeObserver'

 61       ];

 62     var removeFn = removeArr[arguments.length];

 63 

 64     if (_.isFunction(removeFn)) removeFn.call(this, arguments);

 65 

 66   },

 67 

 68   clearMessageGroup: function () {

 69     this.msgGroup = {};

 70   },

 71 

 72   clearNamespace: function (namespace) {

 73     if (this.msgGroup[namespace]) this.msgGroup[namespace] = {};

 74   },

 75 

 76   clearObservers: function (namespace, id) {

 77     if (!this.msgGroup[namespace]) return;

 78     if (!this.msgGroup[namespace][id]) return;

 79     this.msgGroup[namespace][id] = [];

 80   },

 81 

 82   //沒有具體事件句柄便不能被移除

 83   removeObserver: function (namespace, id, handler) {

 84     var i, len, _arr;

 85     if (!this.msgGroup[namespace]) return;

 86     if (!this.msgGroup[namespace][id]) return;

 87     _arr = this.msgGroup[namespace][id];

 88 

 89     for (i = 0, len = _arr.length; i < len; i++) {

 90       if (_arr[i] === handler) _arr[id].splice(i, 1);

 91     }

 92   },

 93 

 94   //觸發各個事件,事件句柄所處作用域需傳入時自己處理

 95   dispatch: function (namespace, id, data, scope) {

 96     var i, len, _arr;

 97 

 98     if (!(namespace && id)) return;

 99 

100     if (!this.msgGroup[namespace]) return;

101     if (!this.msgGroup[namespace][id]) return;

102     _arr = this.msgGroup[namespace][id];

103 

104     for (i = 0, len = _arr.length; i < len; i++) {

105       if (_.isFunction(_arr[i])) _arr[i].call(scope || this, data);

106     }

107   }

108 

109 });

110 

111 Dalmatian.MessageCenter.getInstance = function () {

112   if (!this.instance) {

113     this.instance = new Dalmatian.MessageCenter();

114   }

115   return this.instance;

116 };

117 

118 Dalmatian.MSG = Dalmatian.MESSAGECENTER = Dalmatian.MessageCenter.getInstance();

完了這塊我們怎麼使用了,這裡回到我們的alert與月曆框,讓我們實作他們之間的通信

alert與calendar之間的通信

我們實作這樣的效果,點選alert框時,顯示一個時間,并且月曆上将此日期标紅

PS:每次一到這個時間久累了,代碼未做整理

① 我們在calendar執行個體化的時候便做事件注冊(訂閱)

//事件注冊點,應該單獨封裝

Dalmatian.MSG.register('ui', 'alertShow', $.proxy(function (data) {

  var s = '';

}, this));

② 在每次設定message内容時候便抛出事件

 1 set: function (options) {

 2   _.extend(this.adapter.datamodel, options);

 3   //    this.adapter.datamodel.content = options.content;

 4   this.adapter.notifyDataChanged();

 6   if (options.content) {

 7     Dalmatian.MSG.dispatch('ui', 'alertShow', options.content);

 8   }

10 },

于是便有了這樣的效果,每次設定值的時候,我這裡都會被觸發

而且這裡的this指向的是calendar,是以我們這裡可以做處理,由于時間原因,我這裡便亂幹了

可以看到,每次操作後,calendar得到了更新,但是由于我這裡是直接操作的dom未做datamodel操作,是以沒有做狀态儲存,第二次實際上是該重新整理的

這裡我們暫時不管

 View Code

結語

今天暫時到此,我們下次繼續,本人技術有限,若是文中有任何不足以及問題,請提出

這裡命名空間以及id皆有可能像滾雪球似的越滾越多,是以這裡需要架構本身做出約定,具體看後期實踐吧......

本文轉自葉小钗部落格園部落格,原文連結:http://www.cnblogs.com/yexiaochai/p/3721802.html,如需轉載請自行聯系原作者

繼續閱讀