在 Kendo 中,基類 Class 第一個重要的派生類就是 Observable, 顧名思義,就是一個可觀察的對象,也就是觀察者模式的基礎。
對於觀察者模式來說,應該有主題和觀察者,這里我們討論的其實是主題,觀察者只需要提供一個回調函數,在適當的時候得到回調就可以了。
對於主題來說,我們應該支持多種觀察的目標,如果你使用過 .NET 的事件,這里簡直就是將 .NET 的事件輪子重新實現了一下。
1.事件
_events 是用來保存注冊的事件信息的存儲對象,可以在主題上定義多種事件,每個事件就是 _events 上的一個字段,字段的名字就是事件名稱,值是一個數組,用來保存注冊到這個事件的回調函數。初始化函數中,將這個對象創建出來。
init: function() { this._events = {}; },
2. 注冊
bind 函數用來進行注冊,既可以使用事件名稱,處理器方式,也可以一次性注冊多個事件,使用事件名稱的數組,對應每個事件的處理函數對象來表示。
最關鍵的實際上是這兩行。
events = that._events[eventName] = that._events[eventName] || [];
events.push(handler);
這里有一些特殊的處理,就是可以注冊僅僅執行一次的處理器。在注冊的時候,需要將 one 設置為 true,默認是 undefined,也就是多次的。
在一次的情況下,會自動將用戶注冊的處理器另外保存到 original 中,然后創建一個新的處理器進行注冊,這個處理器在執行一次之后,自動將自己從處理器列表中刪除。
注冊的全部代碼
bind: function(eventName, handlers, one) { var that = this, idx, eventNames = typeof eventName === STRING ? [eventName] : eventName, length, original, handler, handlersIsFunction = typeof handlers === FUNCTION, events; if (handlers === undefined) { for (idx in eventName) { that.bind(idx, eventName[idx]); } return that; } for (idx = 0, length = eventNames.length; idx < length; idx++) { eventName = eventNames[idx]; handler = handlersIsFunction ? handlers : handlers[eventName]; if (handler) { if (one) { original = handler; handler = function() { that.unbind(eventName, handler); original.apply(that, arguments); }; handler.original = original; } events = that._events[eventName] = that._events[eventName] || []; events.push(handler); } } return that; },
3. 取消注冊
對應注冊的就是取消注冊了。
unbind 完成取消注冊的任務,取消注冊的時候,有三種選擇
- 全部取消注冊的觀察者
- 將某個時間的觀察者取消
- 或者單個取消
所以代碼更加簡單明了。original 就是在一次性事件中保存的原有處理器。
unbind: function(eventName, handler) { var that = this, events = that._events[eventName], idx; if (eventName === undefined) { that._events = {}; } else if (events) { if (handler) { for (idx = events.length - 1; idx >= 0; idx--) { if (events[idx] === handler || events[idx].original === handler) { events.splice(idx, 1); } } } else { that._events[eventName] = []; } } return that; }
4. 觸發處理
觸發就比較容易了,提供事件的名稱,事件的參數就可以了,直接遍歷數組中保存的每一個處理器,通過 call 調用將對象自己作為 this 傳遞到處理器中。
trigger: function(eventName, e) { var that = this, events = that._events[eventName], idx, length; if (events) { e = e || {}; e.sender = that; e._defaultPrevented = false; e.preventDefault = preventDefault; e.isDefaultPrevented = isDefaultPrevented; events = events.slice(); for (idx = 0, length = events.length; idx < length; idx++) { events[idx].call(that, e); } return e._defaultPrevented === true; } return false; }
5. 輔助函數
額外還提供了兩個輔助函數,one 和 first
one 用來檢查注冊僅僅執行一次的處理器,你會看到通過直接將 bind 的 one 參數設置為 true 來實現的。
one: function(eventNames, handlers) { return this.bind(eventNames, handlers, true); },
first 用來將處理函數壓入調用對象的最前面, unshift() 方法可向數組的開頭添加一個或更多元素,並返回新的長度。
first: function(eventName, handlers) { var that = this, idx, eventNames = typeof eventName === STRING ? [eventName] : eventName, length, handler, handlersIsFunction = typeof handlers === FUNCTION, events; for (idx = 0, length = eventNames.length; idx < length; idx++) { eventName = eventNames[idx]; handler = handlersIsFunction ? handlers : handlers[eventName]; if (handler) { events = that._events[eventName] = that._events[eventName] || []; events.unshift(handler); } } return that; },
6. 全部代碼
全部代碼如下:
var Observable = Class.extend({ init: function() { this._events = {}; }, bind: function(eventName, handlers, one) { var that = this, idx, eventNames = typeof eventName === STRING ? [eventName] : eventName, length, original, handler, handlersIsFunction = typeof handlers === FUNCTION, events; if (handlers === undefined) { for (idx in eventName) { that.bind(idx, eventName[idx]); } return that; } for (idx = 0, length = eventNames.length; idx < length; idx++) { eventName = eventNames[idx]; handler = handlersIsFunction ? handlers : handlers[eventName]; if (handler) { if (one) { original = handler; handler = function() { that.unbind(eventName, handler); original.apply(that, arguments); }; handler.original = original; } events = that._events[eventName] = that._events[eventName] || []; events.push(handler); } } return that; }, one: function(eventNames, handlers) { return this.bind(eventNames, handlers, true); }, first: function(eventName, handlers) { var that = this, idx, eventNames = typeof eventName === STRING ? [eventName] : eventName, length, handler, handlersIsFunction = typeof handlers === FUNCTION, events; for (idx = 0, length = eventNames.length; idx < length; idx++) { eventName = eventNames[idx]; handler = handlersIsFunction ? handlers : handlers[eventName]; if (handler) { events = that._events[eventName] = that._events[eventName] || []; events.unshift(handler); } } return that; }, trigger: function(eventName, e) { var that = this, events = that._events[eventName], idx, length; if (events) { e = e || {}; e.sender = that; e._defaultPrevented = false; e.preventDefault = preventDefault; e.isDefaultPrevented = isDefaultPrevented; events = events.slice(); for (idx = 0, length = events.length; idx < length; idx++) { events[idx].call(that, e); } return e._defaultPrevented === true; } return false; }, unbind: function(eventName, handler) { var that = this, events = that._events[eventName], idx; if (eventName === undefined) { that._events = {}; } else if (events) { if (handler) { for (idx = events.length - 1; idx >= 0; idx--) { if (events[idx] === handler || events[idx].original === handler) { events.splice(idx, 1); } } } else { that._events[eventName] = []; } } return that; } });
7. 總結
Observable 提供了基本的觀察者模式支持。