原創(chuàng)作者: abruzzi?
接上篇:JavaScript內(nèi)核系列 第8章 面向?qū)ο蟮腏avaScript(上) 8.4實(shí)例:事件分發(fā)器 這一節(jié),我們通過學(xué)習(xí)一個(gè)面向?qū)ο蟮膶?shí)例來對(duì)JavaScript的面向?qū)ο筮M(jìn)行更深入的理解,這個(gè)例子不能太復(fù)雜,涉及到的內(nèi)容也不能僅僅為繼承,多態(tài)等概念,如果那樣,會(huì)失去閱讀的樂趣,最好是在實(shí)例中穿插一些講解,則可以得到最好的效果。
本節(jié)要分析的實(shí)例為一個(gè)事件分發(fā)器(Event Dispatcher),本身來自于一個(gè)實(shí)際項(xiàng)目,但同時(shí)又比較小巧,我對(duì)其代碼做了部分修改,去掉了一些業(yè)務(wù)相關(guān)的部分。
事件分發(fā)器通常是跟UI聯(lián)系在一起的,UI中有多個(gè)組件,它們之間經(jīng)常需要互相通信,當(dāng)UI比較復(fù)雜,而頁(yè)面元素的組織又不夠清晰的時(shí)候,事件的處理會(huì)非常麻煩。在本節(jié)的例子中,事件分發(fā)器為一個(gè)對(duì)象,UI組件發(fā)出事件到事件分發(fā)器,也可以注冊(cè)自己到分發(fā)器,當(dāng)自己關(guān)心的事件到達(dá)時(shí),進(jìn)行響應(yīng)。如果你熟悉設(shè)計(jì)模式的話,會(huì)很快想到觀察者模式,例子中的事件分發(fā)器正式使用了此模式。
?
?
?
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};
?
?
定義一個(gè)名稱空間uikit,并聲明一個(gè)靜態(tài)的常量:EventTypes,此變量定義了目前系統(tǒng)所支持的事件類型。
?
?
?
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; }});
?
?
?
定義事件類,事件包括類型和事件中包含的數(shù)據(jù),通常為事件發(fā)生的點(diǎn)上的一些信息,比如點(diǎn)擊一個(gè)表格的某個(gè)單元格,可能需要將該單元格所在的行號(hào)和列號(hào)包裝進(jìn)事件的數(shù)據(jù)。
?
?
?
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; }});
?
?
?
定義事件監(jiān)聽器類,事件監(jiān)聽器包含兩個(gè)屬性,及監(jiān)聽器所關(guān)心的事件類型sense和當(dāng)該類型的事件發(fā)生后要做的動(dòng)作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(); };
?
?
?
這里定義了一個(gè)單例的事件分發(fā)器,同一個(gè)系統(tǒng)中的任何組件都可以向此實(shí)例注冊(cè)自己,或者發(fā)送事件到此實(shí)例。事件分發(fā)器事實(shí)上需要為何這樣一個(gè)數(shù)據(jù)結(jié)構(gòu):
?
?
?
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();}} ],..};
?
?
當(dāng)事件發(fā)生之后,分發(fā)器會(huì)找到該事件處理器的數(shù)組,然后依次調(diào)用監(jiān)聽器的handle方法進(jìn)行相應(yīng)。好了,到此為止,我們已經(jīng)有了事件分發(fā)器的基本框架了,下來,我們開始實(shí)現(xiàn)我們的組件(Component)。
???????? 組件要通信,則需要加入事件支持,因此可以抽取出一個(gè)類:
?
?
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); }});
?
?
?
繼承了這個(gè)類的類具有事件支持的能力,可以raise事件,也可以注冊(cè)監(jiān)聽器,這個(gè)EventSupport僅僅做了一個(gè)代理,將實(shí)際的工作代理到事件分發(fā)器上。
?
?
?
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){}});
?
?
?
定義所有的組件的基類,一般而言,組件需要有一個(gè)畫布(canvas)的屬性,而且組件需要有展現(xiàn)自己的能力,因此需要實(shí)現(xiàn)render方法來畫出自己來。
?
我們來看一個(gè)繼承了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; }});
?
?
?
首先,我們的畫布其實(shí)是一個(gè)共jQuery選擇的選擇器,選擇到這個(gè)畫布之后,通過jQuery則可以比較容易的在畫布上繪制組件。
?
在我們的實(shí)現(xiàn)中,數(shù)據(jù)與視圖是分離的,我們通過定義這樣的數(shù)據(jù)結(jié)構(gòu):
?
?
?
Js代碼
{items?:?["China",?"Canada",?"U.S.A",?"U.K",?"Uruguay"]};?? {items : ["China", "Canada", "U.S.A", "U.K", "Uruguay"]};
?
?
?
則可以render出如下圖所示的List:
?
?
?
好,既然組件模型已經(jīng)有了,事件分發(fā)器的框架也有了,相信你已經(jīng)迫不及待的想要看看這些代碼可以干點(diǎn)什么了吧,再耐心一下,我們還要寫一點(diǎn)代碼:
?
?
?
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,我們?cè)谖臋n加載完畢之后,新建了兩個(gè)對(duì)象List和Grid,通過點(diǎn)擊List上的條目,如果這些條目在List的模型上索引發(fā)生變化,則會(huì)發(fā)出EVENT_INDEX_CHAGE事件,接收到這個(gè)事件的組件或者DataModel會(huì)做出相應(yīng)的響應(yīng)。在本例中,ldmap在接收到EVENT_INDEX_CHANGE事件后,會(huì)組織數(shù)據(jù),并發(fā)出EVENT_GRID_DATA_READY事件,而Grid接收到這個(gè)事件后,根據(jù)事件對(duì)象上綁定的數(shù)據(jù)模型來更新自己的UI。
上例中的類繼承關(guān)系如下圖:
?
圖 事件分發(fā)器類層次
?
???????? 應(yīng)該注意的是,在綁定完監(jiān)聽器之后,我們手動(dòng)的觸發(fā)了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} });
?
?
?
在實(shí)際的應(yīng)用中,這個(gè)事件可能是用戶在頁(yè)面上點(diǎn)擊一個(gè)按鈕,或者一個(gè)Ajax請(qǐng)求的返回,等等,一旦事件監(jiān)聽器注冊(cè)完畢,程序就已經(jīng)就緒,等待異步事件并響應(yīng)。
?
點(diǎn)擊List中的元素China,Grid中的數(shù)據(jù)發(fā)生變化
?
點(diǎn)擊Canada,Grid中的數(shù)據(jù)同樣發(fā)生相應(yīng)的變化:
?
?
由于List和Grid的數(shù)據(jù)是關(guān)聯(lián)在一起的,他們的數(shù)據(jù)結(jié)構(gòu)具有下列的結(jié)構(gòu):
?
?
?
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" }] ]},...];
?
?
一個(gè)組件可以發(fā)出多種事件,同時(shí)也可以監(jiān)聽多種事件,所以我們可以為L(zhǎng)ist的下標(biāo)改變事件注冊(cè)另一個(gè)監(jiān)聽器,監(jiān)聽器為一個(gè)簡(jiǎn)單組件Panel,當(dāng)接收到這個(gè)事件后,該P(yáng)anel會(huì)根據(jù)一個(gè)隨機(jī)的顏色來重置自身的背景色(注意在List和Grid下面的灰色Panel):
?
轉(zhuǎn)載于:https://www.cnblogs.com/TDYToBaby/archive/2010/06/12/1757326.html
總結(jié)
以上是生活随笔 為你收集整理的JavaScript内核系列 第8章 面向对象的JavaScript(下) 的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔 推薦給好友。