原創作者: abruzzi?
接上篇:JavaScript內核系列 第8章 面向對象的JavaScript(上) 8.4實例:事件分發器 這一節,我們通過學習一個面向對象的實例來對JavaScript的面向對象進行更深入的理解,這個例子不能太復雜,涉及到的內容也不能僅僅為繼承,多態等概念,如果那樣,會失去閱讀的樂趣,最好是在實例中穿插一些講解,則可以得到最好的效果。
本節要分析的實例為一個事件分發器(Event Dispatcher),本身來自于一個實際項目,但同時又比較小巧,我對其代碼做了部分修改,去掉了一些業務相關的部分。
事件分發器通常是跟UI聯系在一起的,UI中有多個組件,它們之間經常需要互相通信,當UI比較復雜,而頁面元素的組織又不夠清晰的時候,事件的處理會非常麻煩。在本節的例子中,事件分發器為一個對象,UI組件發出事件到事件分發器,也可以注冊自己到分發器,當自己關心的事件到達時,進行響應。如果你熟悉設計模式的話,會很快想到觀察者模式,例子中的事件分發器正式使用了此模式。
?
?
?
Js代碼
var?uikit?=?uikit?||?{};?? uikit.event?=?uikit.event?||?{};?? ??? uikit.event.EventTypes?=?{?? ????EVENT_NONE?:?0,?? ????EVENT_INDEX_CHANGE?:?1,?? ????EVENT_LIST_DATA_READY?:?2,?? ????EVENT_GRID_DATA_READY?:?3?? };?? var uikit = uikit || {};uikit.event = uikit.event || {}; uikit.event.EventTypes = { EVENT_NONE : 0, EVENT_INDEX_CHANGE : 1, EVENT_LIST_DATA_READY : 2, EVENT_GRID_DATA_READY : 3};
?
?
定義一個名稱空間uikit,并聲明一個靜態的常量:EventTypes,此變量定義了目前系統所支持的事件類型。
?
?
?
Js代碼
uikit.event.JSEvent?=?Base.extend({?? ????constructor?:?function(obj){?? ???????this.type?=?obj.type?||?uikit.event.EventTypes.EVENT_NONE;?? ???????this.object?=?obj.data?||?{};?? ????},?? ????? ????getType?:?function(){?? ???????return?this.type;?? ????},?? ????? ????getObject?:?function(){?? ???????return?this.object;?? ????}?? });?? uikit.event.JSEvent = Base.extend({ constructor : function(obj){ this.type = obj.type || uikit.event.EventTypes.EVENT_NONE; this.object = obj.data || {}; }, getType : function(){ return this.type; }, getObject : function(){ return this.object; }});
?
?
?
定義事件類,事件包括類型和事件中包含的數據,通常為事件發生的點上的一些信息,比如點擊一個表格的某個單元格,可能需要將該單元格所在的行號和列號包裝進事件的數據。
?
?
?
Js代碼
uikit.event.JSEventListener?=?Base.extend({?? ????constructor?:?function(listener){?? ???????this.sense?=?listener.sense;?? ???????this.handle?=?listener.handle?||?function(event){};?? ????},?? ????? ????getSense?:?function(){?? ???????return?this.sense;?? ????}?? });?? uikit.event.JSEventListener = Base.extend({ constructor : function(listener){ this.sense = listener.sense; this.handle = listener.handle || function(event){}; }, getSense : function(){ return this.sense; }});
?
?
?
定義事件監聽器類,事件監聽器包含兩個屬性,及監聽器所關心的事件類型sense和當該類型的事件發生后要做的動作handle。
?
?
?
Js代碼
uikit.event.JSEventDispatcher?=?function(){?? ????if(uikit.event.JSEventDispatcher.singlton){?? ???????return?uikit.event.JSEventDispatcher.singlton;?? ????}?? ??? ????this.listeners?=?{};?? ??? ????uikit.event.JSEventDispatcher.singlton?=?this;?? ??? ????this.post?=?function(event){?? ???????var?handlers?=?this.listeners[event.getType()];?? ???????for(var?index?in?handlers){?? ???????????if(handlers[index].handle?&&?typeof?handlers[index].handle?==?"function")?? ???????????handlers[index].handle(event);?? ???????}?? ????};?? ??? ????this.addEventListener?=?function(listener){?? ???????var?item?=?listener.getSense();?? ???????var?listeners?=?this.listeners[item];?? ???????if(listeners){?? ???????????this.listeners[item].push(listener);?? ???????}else{?? ???????????var?hList?=?new?Array();?? ???????????hList.push(listener);?? ???????????this.listeners[item]?=?hList;???? ???????}?? ????};?? }?? ??? uikit.event.JSEventDispatcher.getInstance?=?function(){?? ????return?new?uikit.event.JSEventDispatcher();???? };?? uikit.event.JSEventDispatcher = function(){ if(uikit.event.JSEventDispatcher.singlton){ return uikit.event.JSEventDispatcher.singlton; } this.listeners = {}; uikit.event.JSEventDispatcher.singlton = this; this.post = function(event){ var handlers = this.listeners[event.getType()]; for(var index in handlers){ if(handlers[index].handle && typeof handlers[index].handle == "function") handlers[index].handle(event); } }; this.addEventListener = function(listener){ var item = listener.getSense(); var listeners = this.listeners[item]; if(listeners){ this.listeners[item].push(listener); }else{ var hList = new Array(); hList.push(listener); this.listeners[item] = hList; } };} uikit.event.JSEventDispatcher.getInstance = function(){ return new uikit.event.JSEventDispatcher(); };
?
?
?
這里定義了一個單例的事件分發器,同一個系統中的任何組件都可以向此實例注冊自己,或者發送事件到此實例。事件分發器事實上需要為何這樣一個數據結構:
?
?
?
Js代碼
var?listeners?=?{?? ????eventType.foo?:?[?? ???????{sense?:?"eventType.foo",?handle?:?function(){doSomething();}}?? ???????{sense?:?"eventType.foo",?handle?:?function(){doSomething();}}?? ???????{sense?:?"eventType.foo",?handle?:?function(){doSomething();}}?? ????],?? ????eventType.bar?:?[?? ???????{sense?:?"eventType.bar",?handle?:?function(){doSomething();}}?? ???????{sense?:?"eventType.bar",?handle?:?function(){doSomething();}}?? ???????{sense?:?"eventType.bar",?handle?:?function(){doSomething();}}?? ????],..?? };?? var listeners = { eventType.foo : [ {sense : "eventType.foo", handle : function(){doSomething();}} {sense : "eventType.foo", handle : function(){doSomething();}} {sense : "eventType.foo", handle : function(){doSomething();}} ], eventType.bar : [ {sense : "eventType.bar", handle : function(){doSomething();}} {sense : "eventType.bar", handle : function(){doSomething();}} {sense : "eventType.bar", handle : function(){doSomething();}} ],..};
?
?
當事件發生之后,分發器會找到該事件處理器的數組,然后依次調用監聽器的handle方法進行相應。好了,到此為止,我們已經有了事件分發器的基本框架了,下來,我們開始實現我們的組件(Component)。
???????? 組件要通信,則需要加入事件支持,因此可以抽取出一個類:
?
?
Js代碼
uikit.component?=?uikit.component?||?{};?? ??? uikit.component.EventSupport?=?Base.extend({?? ??constructor?:?function(){?? ????? ??},?? ??? ??raiseEvent?:?function(eventdef){?? ???????var?e?=?new?uikit.event.JSEvent(eventdef);?? ???????uikit.event.JSEventDispatcher.getInstance().post(e);?????? ??},?? ??? ??addActionListener?:?function(listenerdef){?? ???????var?l?=?new?uikit.event.JSEventListener(listenerdef);?? ???????uikit.event.JSEventDispatcher.getInstance().addEventListener(l);?? ??}?? });?? uikit.component = uikit.component || {}; uikit.component.EventSupport = Base.extend({ constructor : function(){ }, raiseEvent : function(eventdef){ var e = new uikit.event.JSEvent(eventdef); uikit.event.JSEventDispatcher.getInstance().post(e); }, addActionListener : function(listenerdef){ var l = new uikit.event.JSEventListener(listenerdef); uikit.event.JSEventDispatcher.getInstance().addEventListener(l); }});
?
?
?
繼承了這個類的類具有事件支持的能力,可以raise事件,也可以注冊監聽器,這個EventSupport僅僅做了一個代理,將實際的工作代理到事件分發器上。
?
?
?
Js代碼
uikit.component.ComponentBase?=?uikit.component.EventSupport.extend({?? ??constructor:?function(canvas)?{?? ???????this.canvas?=?canvas;?? ??},?? ??? ??render?:?function(datamodel){}?? });?? uikit.component.ComponentBase = uikit.component.EventSupport.extend({ constructor: function(canvas) { this.canvas = canvas; }, render : function(datamodel){}});
?
?
?
定義所有的組件的基類,一般而言,組件需要有一個畫布(canvas)的屬性,而且組件需要有展現自己的能力,因此需要實現render方法來畫出自己來。
?
我們來看一個繼承了ComponentBase的類JSList:
?
?
?
Js代碼
uikit.component.JSList?=?uikit.component.ComponentBase.extend({?? ????constructor?:?function(canvas,?datamodel){?? ???????this.base(canvas);?? ???????this.render(datamodel);?? ????},?? ????? ????render?:?function(datamodel){?? ???????var?jqo?=?$(this.canvas);?? ???????var?text?=?"";?? ???????for(var?p?in?datamodel.items){?? ???????????text?+=?datamodel.items[p]?+?";";?? ???????}?? ???????var?item?=?$("<div></div>").addClass("component");?? ???????item.text(text);?? ???????item.click(function(){?? ???????????jqo.find("div.selected").removeClass("selected");?? ???????????$(this).addClass("selected");?? ???????????? ???????????var?idx?=?jqo.find("div").index($(".selected")[0]);?? ???????????var?c?=?new?uikit.component.ComponentBase(null);?? ???????????c.raiseEvent({?? ??????????????type?:?uikit.event.EventTypes.EVENT_INDEX_CHANGE,?? ??????????????data?:?{index?:?idx}?? ???????????});?? ???????});?? ???????? ???????jqo.append(item);?? ????},?? ????? ????update?:?function(event){?? ???????var?jqo?=?$(this.canvas);?? ???????jqo.empty();?? ???????var?dm?=?event.getObject().items;?? ??? ???????for(var?i?=?0;?i?<?dm.length();i++){?? ???????????var?entity?=?dm.get(i).item;?? ???????????jqo.append(this.createItem({items?:?entity}));?? ???????}?? ????},?? ????? ????createItem?:?function(datamodel){?? ???????var?jqo?=?$(this.canvas);?? ???????var?text?=?datamodel.items;?? ??? ???????var?item?=?$("<div></div>").addClass("component");?? ???????item.text(text);?? ???????item.click(function(){?? ???????????jqo.find("div.selected").removeClass("selected");?? ???????????$(this).addClass("selected");?? ???????????? ???????????var?idx?=?jqo.find("div").index($(".selected")[0]);?? ???????????var?c?=?new?uikit.component.ComponentBase(null);?? ???????????c.raiseEvent({?? ??????????????type?:?uikit.event.EventTypes.EVENT_INDEX_CHANGE,?? ??????????????data?:?{index?:?idx}?? ???????????});?? ???????});?? ???????? ???????return?item;?? ????},?? ????? ????getSelectedItemIndex?:?function(){?? ???????var?jqo?=?$(this.canvas);?? ???????var?index?=?jqo.find("div").index($(".selected")[0]);?? ???????return?index;?? ????}?? });?? uikit.component.JSList = uikit.component.ComponentBase.extend({ constructor : function(canvas, datamodel){ this.base(canvas); this.render(datamodel); }, render : function(datamodel){ var jqo = $(this.canvas); var text = ""; for(var p in datamodel.items){ text += datamodel.items[p] + ";"; } var item = $("<div></div>").addClass("component"); item.text(text); item.click(function(){ jqo.find("div.selected").removeClass("selected"); $(this).addClass("selected"); var idx = jqo.find("div").index($(".selected")[0]); var c = new uikit.component.ComponentBase(null); c.raiseEvent({ type : uikit.event.EventTypes.EVENT_INDEX_CHANGE, data : {index : idx} }); }); jqo.append(item); }, update : function(event){ var jqo = $(this.canvas); jqo.empty(); var dm = event.getObject().items; for(var i = 0; i < dm.length();i++){ var entity = dm.get(i).item; jqo.append(this.createItem({items : entity})); } }, createItem : function(datamodel){ var jqo = $(this.canvas); var text = datamodel.items; var item = $("<div></div>").addClass("component"); item.text(text); item.click(function(){ jqo.find("div.selected").removeClass("selected"); $(this).addClass("selected"); var idx = jqo.find("div").index($(".selected")[0]); var c = new uikit.component.ComponentBase(null); c.raiseEvent({ type : uikit.event.EventTypes.EVENT_INDEX_CHANGE, data : {index : idx} }); }); return item; }, getSelectedItemIndex : function(){ var jqo = $(this.canvas); var index = jqo.find("div").index($(".selected")[0]); return index; }});
?
?
?
首先,我們的畫布其實是一個共jQuery選擇的選擇器,選擇到這個畫布之后,通過jQuery則可以比較容易的在畫布上繪制組件。
?
在我們的實現中,數據與視圖是分離的,我們通過定義這樣的數據結構:
?
?
?
Js代碼
{items?:?["China",?"Canada",?"U.S.A",?"U.K",?"Uruguay"]};?? {items : ["China", "Canada", "U.S.A", "U.K", "Uruguay"]};
?
?
?
則可以render出如下圖所示的List:
?
?
?
好,既然組件模型已經有了,事件分發器的框架也有了,相信你已經迫不及待的想要看看這些代碼可以干點什么了吧,再耐心一下,我們還要寫一點代碼:
?
?
?
Js代碼
$(document).ready(function(){?? ????var?ldmap?=?new?uikit.component.ArrayLike(dataModel);?? ????? ????ldmap.addActionListener({?? ???????sense?:?uikit.event.EventTypes.EVENT_INDEX_CHANGE,?? ???????handle?:?function(event){?? ???????????var?idx?=?event.getObject().index;?? ???????????uikit.component.EventGenerator.raiseEvent({?? ??????????????type?:?uikit.event.EventTypes.EVENT_GRID_DATA_READY,?? ??????????????data?:?{rows?:?ldmap.get(idx).grid}?? ???????????});?? ???????}?? ????});?? ????? ????var?list?=?new?uikit.component.JSList("div#componentList",?[]);?? ????var?grid?=?new?uikit.component.JSGrid("div#conditionsTable?table?tbody");?? ????? ????list.addActionListener({?? ????????sense?:??uikit.event.EventTypes.EVENT_LIST_DATA_READY,?? ????????handle?:?function(event){?? ????????????list.update(event);?? ????????}?? ????});?? ??? ????grid.addActionListener({?? ???????sense?:?uikit.event.EventTypes.EVENT_GRID_DATA_READY,?? ???????handle?:?function(event){?? ???????????grid.update(event);?? ???????}?? ????});?? ??? ????uikit.component.EventGenerator.raiseEvent({?? ???????type?:?uikit.event.EventTypes.EVENT_LIST_DATA_READY,?? ???????data?:?{items?:?ldmap}?? ????});?? ??? ????var?colorPanel?=?new?uikit.component.Panel("div#colorPanel");?? ????colorPanel.addActionListener({?? ???????sense?:?uikit.event.EventTypes.EVENT_INDEX_CHANGE,?? ???????handle?:?function(event){?? ???????????var?idx?=?parseInt(10*Math.random())?? ???????????colorPanel.update(idx);?? ???????}?? ????});?? });?? $(document).ready(function(){ var ldmap = new uikit.component.ArrayLike(dataModel); ldmap.addActionListener({ sense : uikit.event.EventTypes.EVENT_INDEX_CHANGE, handle : function(event){ var idx = event.getObject().index; uikit.component.EventGenerator.raiseEvent({ type : uikit.event.EventTypes.EVENT_GRID_DATA_READY, data : {rows : ldmap.get(idx).grid} }); } }); var list = new uikit.component.JSList("div#componentList", []); var grid = new uikit.component.JSGrid("div#conditionsTable table tbody"); list.addActionListener({ sense : uikit.event.EventTypes.EVENT_LIST_DATA_READY, handle : function(event){ list.update(event); } }); grid.addActionListener({ sense : uikit.event.EventTypes.EVENT_GRID_DATA_READY, handle : function(event){ grid.update(event); } }); uikit.component.EventGenerator.raiseEvent({ type : uikit.event.EventTypes.EVENT_LIST_DATA_READY, data : {items : ldmap} }); var colorPanel = new uikit.component.Panel("div#colorPanel"); colorPanel.addActionListener({ sense : uikit.event.EventTypes.EVENT_INDEX_CHANGE, handle : function(event){ var idx = parseInt(10*Math.random()) colorPanel.update(idx); } });}); ?
?
?
使用jQuery,我們在文檔加載完畢之后,新建了兩個對象List和Grid,通過點擊List上的條目,如果這些條目在List的模型上索引發生變化,則會發出EVENT_INDEX_CHAGE事件,接收到這個事件的組件或者DataModel會做出相應的響應。在本例中,ldmap在接收到EVENT_INDEX_CHANGE事件后,會組織數據,并發出EVENT_GRID_DATA_READY事件,而Grid接收到這個事件后,根據事件對象上綁定的數據模型來更新自己的UI。
上例中的類繼承關系如下圖:
?
圖 事件分發器類層次
?
???????? 應該注意的是,在綁定完監聽器之后,我們手動的觸發了EVENT_LIST_DATA_READY事件,來通知List可以繪制自身了:
?
?
?
Js代碼
uikit.component.EventGenerator.raiseEvent({?? ???type?:?uikit.event.EventTypes.EVENT_LIST_DATA_READY,?? ???data?:?{items?:?ldmap}?? });?? uikit.component.EventGenerator.raiseEvent({ type : uikit.event.EventTypes.EVENT_LIST_DATA_READY, data : {items : ldmap} });
?
?
?
在實際的應用中,這個事件可能是用戶在頁面上點擊一個按鈕,或者一個Ajax請求的返回,等等,一旦事件監聽器注冊完畢,程序就已經就緒,等待異步事件并響應。
?
點擊List中的元素China,Grid中的數據發生變化
?
點擊Canada,Grid中的數據同樣發生相應的變化:
?
?
由于List和Grid的數據是關聯在一起的,他們的數據結構具有下列的結構:
?
?
?
Js代碼
var?dataModel?=?[{?? ????item:?"China",?? ????grid:?[?? ????????[{?? ????????????dname:?"Beijing",?? ????????????type:?"string"?? ????????},?? ????????{?? ????????????dname:?"ProductA",?? ????????????type:?"string"?? ????????},?? ????????{?? ????????????dname:?1000,?? ????????????type:?"number"?? ????????}],?? ????????[{?? ????????????dname:?"ShangHai",?? ????????????type:?"string"?? ????????},?? ????????{?? ????????????dname:?"ProductB",?? ????????????type:?"string"?? ????????},?? ????????{?? ????????????dname:?23451,?? ????????????type:?"number"?? ????????}],?? ????????[{?? ????????????dname:?"GuangZhou",?? ????????????type:?"string"?? ????????},?? ????????{?? ????????????dname:?"ProductB",?? ????????????type:?"string"?? ????????},?? ????????{?? ????????????dname:?87652,?? ????????????type:?"number"?? ????????}]?? ????]?? },...?? ];?? var dataModel = [{ item: "China", grid: [ [{ dname: "Beijing", type: "string" }, { dname: "ProductA", type: "string" }, { dname: 1000, type: "number" }], [{ dname: "ShangHai", type: "string" }, { dname: "ProductB", type: "string" }, { dname: 23451, type: "number" }], [{ dname: "GuangZhou", type: "string" }, { dname: "ProductB", type: "string" }, { dname: 87652, type: "number" }] ]},...];
?
?
一個組件可以發出多種事件,同時也可以監聽多種事件,所以我們可以為List的下標改變事件注冊另一個監聽器,監聽器為一個簡單組件Panel,當接收到這個事件后,該Panel會根據一個隨機的顏色來重置自身的背景色(注意在List和Grid下面的灰色Panel):
?
轉載于:https://www.cnblogs.com/TDYToBaby/archive/2010/06/12/1757326.html
總結
以上是生活随笔 為你收集整理的JavaScript内核系列 第8章 面向对象的JavaScript(下) 的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網站內容還不錯,歡迎將生活随笔 推薦給好友。