鍍金池/ 教程/ HTML/ 面向?qū)ο蟮?Javascript
面向?qū)ο蟮?Javascript
客戶端的 JavaScript
概述
核心概念深入
函數(shù)式的 Javascript
對象與 JSON
前端 JavaScript 框架
基本概念
數(shù)組
閉包
正則表達(dá)式
函數(shù)

面向?qū)ο蟮?Javascript

面向?qū)ο缶幊趟枷朐谔岢鲋?,很快就流行起來了,它將開發(fā)人員從冗長,繁復(fù),難以調(diào)試的過程式程序中解放了出來,過程式語 言如 C ,代碼的形式往往如此:

C代碼

Component comp;  
init_component(& comp, props);  

而面向?qū)ο蟮恼Z言如 Java ,則會是這種形式:

Component comp;  
comp.init(props);  

可以看出,方法是對象的方法,對象是方法的對象,這樣的代碼形式更接近人的思維方式,因此 OO 大行其道也并非僥幸。

JavaScript 本身是基于對象 的,而并非基于類。但是, JavaScript 的函數(shù)式語言的特性使得它本身是可編程 的,它可以變成你想要的任何形式。我們在這一章詳細(xì)討論如何使用 JavaScript 進(jìn)行 OO 風(fēng)格的代碼開發(fā)。

原型繼承

JavaScript 中的繼承可以通過原型鏈來實(shí)現(xiàn),調(diào)用對象上的一個(gè)方法,由于方法在 JavaScript 對象中是對另一個(gè)函數(shù)對象的引用,因此解釋器會在對象中查找該屬性,如果沒有找到,則在其內(nèi)部對象 prototype 對象上搜索,由于 prototype 對象與對象本身的結(jié)構(gòu)是一樣的,因此這個(gè)過程會一直回溯到發(fā)現(xiàn)該屬性,則調(diào)用該屬性,否則,報(bào)告一個(gè)錯(cuò)誤。關(guān)于原型繼承,我們不妨看一個(gè)小例 子:

function Base(){  
    this .baseFunc = function (){  
       print ( "base behavior" );  
    }  
}  

function Middle(){  
    this .middleFunc = function (){  
       print ( "middle behavior" );  
    }  
}  

Middle. prototype = new Base();  

function Final(){  
    this .finalFunc = function (){  
       print ( "final behavior" );  
    }  
}  
Final. prototype = new Middle();  

function test(){  
    var obj = new Final();  
    obj.baseFunc();  
    obj.middleFunc();  
    obj.finalFunc();  
}

http://wiki.jikexueyuan.com/project/javascript-core/images/js10.png" alt="" />

圖 原型鏈的示意圖

在 function test 中,我們 new 了一個(gè) Final 對象,然后依次調(diào)用 obj.baseFunc ,由于 obj 對象上并無此方法,則按照上邊提到的規(guī)則,進(jìn)行回溯,在其原型鏈上搜索,由于 Final 的原型鏈上包含 Middle ,而 Middle 上又包含 Base ,因此會執(zhí)行這個(gè)方法,這樣就實(shí)現(xiàn)了類的繼承。

base behavior
middle behavior
final behavior

但是這種繼承形式與傳統(tǒng)的 OO 語言大相徑庭,初學(xué)者很難適應(yīng),我們后邊的章節(jié)會涉及到一個(gè)比較好的 JavaScript 的面向?qū)ο蠡A(chǔ)包 Base ,使用 Base 包,雖然編碼風(fēng)格上會和傳統(tǒng)的 OO 語言不同,但是讀者很快就會發(fā)現(xiàn)這種風(fēng)格的好處。

引用

引用是一個(gè)比較有意思的主題,跟其他的語言不同的是, JavaScript 中的引用始終指向最終的對象,而并非引用本身,我們來看一個(gè)例子:

<strong>var obj = {}; // 空對象  
var ref = obj; // 引用  

obj. name = "objectA" ;  
print ( ref . name ); //ref 跟著添加了 name 屬性  

obj = [ "one" , "two" , "three" ]; //obj 指向了另一個(gè)對象 ( 數(shù)組對象 )  
print ( ref . name ); //ref 還指向原來的對象  
print (obj. length ); //3  
print ( ref . length ); //undefined</strong>  

運(yùn)行結(jié)果如下:

objectA
objectA
3
undefined

obj 只是對一個(gè)匿名對象的引用,所以, ref 并非指向它,當(dāng) obj 指向另一個(gè)數(shù)組對象時(shí) 可以看到,引用 ref 并未改變,而始終指向這那個(gè)后來添加了 name 屬性的 " 空 " 對象 ”{}” 。理解這一點(diǎn)對后邊的內(nèi)容有很大的幫助。

再看這個(gè)例子:

<strong>var obj = {}; // 新建一個(gè)對象,并被 obj 引用  

var ref1 = obj; //ref1 引用 obj, 事實(shí)上是引用 obj 引用的空對象  
var ref2 = obj;  

obj.func = "function" ;  

print (ref1.func);  
print (ref2.func);</strong>  

聲明一個(gè)對象,然后用兩個(gè)引用來引用這個(gè)對象,然后修改原始的對象,注意這兩步的順序,運(yùn)行之:

function
function

根據(jù)運(yùn)行結(jié)果我們可以看出,在定義了引用之后,修改原始的那個(gè)對象會影響到其引用上,這一點(diǎn)也應(yīng)該注意。

new 操作符

有面向?qū)ο缶幊痰幕A(chǔ)有時(shí)會成為一種負(fù)擔(dān),比如看到 new 的時(shí)候, Java 程序員可能會認(rèn)為這將會調(diào)用一個(gè)類的構(gòu)造器構(gòu)造一個(gè)新的對象出來,我們來看一個(gè)例子:

function Shape(type){  
    this .type = type || "rect" ;  
    this .calc = function (){  
       return "calc, " + this .type;  
    }  
}  

var triangle = new Shape( "triangle" );  
print (triangle.calc());  

var circle = new Shape( "circle" );  
print (circle.calc()); 

運(yùn)行結(jié)果如下:

calc, triangle
calc, circle

Java 程序員可能會覺得 Shape 就是一個(gè)類,然后 triangle , circle 即是 Shape 對應(yīng)的具體對象,而其實(shí) JavaScript 并非如此工作的,罪魁禍?zhǔn)准礊榇?new 操作符。在 JavaScript 中,通過 new 操作符來作用與一個(gè)函數(shù),實(shí)質(zhì)上會發(fā)生這樣的動作:

首先,創(chuàng)建一個(gè)空對象,然后用函數(shù)的 apply 方法,將這個(gè)空對象傳入作為 apply 的第一個(gè)參數(shù),及上下文參數(shù)。這樣函數(shù)內(nèi)部的 this 將會被這個(gè)空的對象所替代:

var triangle = new Shape( "triangle" );  
// 上一句相當(dāng)于下面的代碼  
var triangle = {};  
Shape.apply(triangle, [ "triangle" ]);

封裝

事實(shí)上,我們可以通過 JavaScript 的函數(shù)實(shí)現(xiàn)封裝,封裝的好處在于未經(jīng)授權(quán)的客戶代碼無法訪問到我們不公開的數(shù)據(jù),我們來看這個(gè)例子:

function Person(name){  
    //private variable  
    var address = "The Earth" ;  

    //public method  
    this .getAddress = function (){  
       return address;  
    }  

    //public variable  
    this .name = name;  
}  

//public  
Person.prototype.getName = function (){  
    return this .name;  
}  

//public  
Person.prototype.setName = function (name){  
    this .name = name;  
} 

首先聲明一個(gè)函數(shù),作為模板,用面向?qū)ο蟮男g(shù)語來講,就是一個(gè)類 。用 var 方式聲明的變量僅在類內(nèi)部可見,所以 address 為一個(gè)私有成員,訪問 address 的唯一方法是通過我們向外暴露的 getAddress 方法,而 get/setName ,均為原型鏈上的方法,因此為公開的。我們可以做個(gè)測試:

var jack = new Person( "jack" );  
print(jack.name);//jack  
print(jack.getName());//jack  
print(jack.address);//undefined  
print(jack.getAddress());//The Earth

直接通過 jack.address 來訪問 address 變量會得到 undefined 。我們只能通過 jack.getAddress 來訪問。這樣, address 這個(gè)成員就被封裝起來了。 另外需要注意的一點(diǎn)是,我們可以為類添加靜態(tài)成員,這個(gè)過程也很簡單,只需要為函數(shù)對象添加一個(gè) 屬性即可。比如:

function Person(name){  
    //private variable  
    var address = "The Earth" ;  

    //public method  
    this .getAddress = function (){  
       return address;  
    }  

    //public variable  
    this .name = name;  
}  

Person.TAG = "javascript-core" ;// 靜態(tài)變量  

print(Person.TAG);

也就是說,我們在訪問 Person.TAG 時(shí), 不需要實(shí)例化 Person 類。這與 傳統(tǒng)的面向?qū)ο笳Z言如 Java 中的靜態(tài)變量是一致的。

工具包 Base

Base 是由 Dean Edwards 開發(fā)的一個(gè) JavaScript 的 面向?qū)ο蟮幕A(chǔ)包, Base 本身很小,只有 140 行,但是這個(gè)很小的包對面向?qū)ο缶幊田L(fēng)格有很好的支持,支持類的定義,封裝,繼承,子類調(diào)用 父類的方法等,代碼的質(zhì)量也很高,而且很多項(xiàng)目都在使用 Base 作為底 層的支持。盡管如此, JavaScript 的面向?qū)ο箫L(fēng)格依然非常古 怪,并不可以完全和傳統(tǒng)的 OO 語言對等起來。

下面我們來看幾個(gè)基于 Base 的例子, 假設(shè)我們現(xiàn)在在開發(fā)一個(gè)任務(wù)系統(tǒng),我們需要抽象出一個(gè)類來表示任務(wù),對應(yīng)的,每個(gè)任務(wù)都可能會有一個(gè)監(jiān)聽器,當(dāng)任務(wù)執(zhí)行之后,需要通知監(jiān)聽器。我們首先定 義一個(gè)事件監(jiān)聽器的類,然后定義一個(gè)任務(wù)類:

var EventListener = Base.extend({  
    constructor : function(sense){  
       this.sense = sense;  
    },  
    sense : null,  
    handle : function(){  
       print(this.sense+" occured");  
    }  
});  

var Task = Base.extend({  
    constructor : function(name){  
       this.name = name;  
    },  
    name : null,  
    listener : null,  
    execute : function(){  
       print(this.name);  
       this.listener.handle();  
    },  
    setListener : function(listener){  
       this.listener = listener;  
    }  
});

創(chuàng)建類的方式很簡單,需要給 Base.extend 方 法傳入一個(gè) JSON 對象,其中可以有成員和方法。方法訪問自身的成員時(shí) 需要加 this 關(guān)鍵字。而每一個(gè)類都會有一個(gè) constructor 的方法,即構(gòu)造方法。比如事件監(jiān)聽器類 (EventListener) 的構(gòu)造器需要傳入一個(gè)字符串,而任務(wù)類 (Task) 也需要傳入任務(wù)的名字來進(jìn)行構(gòu)造。好了,既然我們已經(jīng)有了任務(wù)類和事件監(jiān)聽器類,我們 來實(shí)例化它們:

var printing = new Task("printing");  
var printEventListener = new EventListener("printing");  
printing.setListener(printEventListener);  
printing.execute();

首先,創(chuàng)建一個(gè)新的 Task , 做打印工作,然后新建一個(gè)事件監(jiān)聽器,并將它注冊在新建的任務(wù)上,這樣,當(dāng)打印發(fā)生時(shí),會通知監(jiān)聽器,監(jiān)聽器會做出相應(yīng)的判斷:

printing
printing occurred

既然有了基本的框架,我們就來使用這個(gè)框架,假設(shè)我們要從 HTTP 服務(wù)器上下載一個(gè)頁面,于是我們設(shè)計(jì)了一個(gè)新的任務(wù)類型,叫做 HttpRequester :

var HttpRequester = Task.extend({  
    constructor : function(name, host, port){  
       this.base(name);  
       this.host = host;  
       this.port = port;  
    },  
    host : "127.0.0.1",  
    port : 9527,  
    execute : function(){  
       print("["+this.name+"] request send to "+this.host+" of port "+this.port);  
       this.listener.handle();  
    }  
});

HttpRequester 類繼承了 Task ,并且重載了 Task 類 的 execute 方法, setListener 方法的內(nèi)容與父類一致,因此不需要重載。

var requester = new HttpRequester("requester1", "127.0.0.1", 8752);  
var listener = new EventListener("http_request");  
requester.setListener(listener);  
requester.execute();

我們新建一個(gè) HttpRequester 任 務(wù),然后注冊上事件監(jiān)聽器,并執(zhí)行之:

[requester1] request send to 127.0.0.1 of port 8752
http_request occured

應(yīng)該注意到 HttpRequester 類 的構(gòu)造器中,有這樣一個(gè)語句:

this.base(name);

表示執(zhí)行父類的構(gòu)造器,即將 name 賦 值給父類的成員變量 name ,這樣在 HttpRequester 的實(shí)例中,我們就可以通過 this.name 來訪問這個(gè)成員了。這套機(jī)制簡直與在其他傳統(tǒng)的 OO 語言并無二致。同時(shí), HttpRequester 類 的 execute 方法覆蓋了父類的 execute 方法,用面向?qū)ο蟮男g(shù)語來講,叫做重載。 在很多應(yīng)用中,有些對象不會每次都創(chuàng)建新的實(shí)例,而是使用一個(gè)固有的實(shí)例,比如提供數(shù)據(jù)源的服務(wù),報(bào)表渲染引擎,事件分發(fā) 器等,每次都實(shí)例化一個(gè)會有很大的開銷,因此人們設(shè)計(jì)出了單例模式,整個(gè)應(yīng)用的生命周期中,始終只有頂多一個(gè)實(shí)例存在。 Base 同樣可以模擬出這樣的能力:

var ReportEngine = Base.extend({  
    constructor : null,  
    run : function(){  
       //render the report  
    }    
});

實(shí)例:事件分發(fā)器

這一節(jié),我們通過學(xué)習(xí)一個(gè)面向?qū)ο蟮膶?shí)例來對 JavaScript 的面向?qū)ο筮M(jìn)行更深入的理解,這個(gè)例子不能太復(fù)雜,涉及到的內(nèi)容也不能僅僅為繼承,多態(tài)等概念,如果那樣,會失去閱讀的樂趣,最好是在實(shí)例中穿插一些講解,則可以得到最好的效果。

本節(jié)要分析的實(shí)例為一個(gè)事件分發(fā)器(Event Dispatcher),本身來自于一個(gè)實(shí)際項(xiàng)目,但同時(shí)又比較小巧,我對其代碼做了部分修改,去掉了一些業(yè)務(wù)相關(guān)的部分。

事件分發(fā)器通常是跟UI聯(lián)系在一起的,UI中有多個(gè)組件,它們之間經(jīng)常需要互相通信,當(dāng)UI比較復(fù)雜,而頁面元素的組織又不夠清晰的時(shí)候,事件的處理會非常麻煩。在本節(jié)的例子中,事件分發(fā)器為一個(gè)對象,UI組件發(fā)出事件到事件分發(fā)器,也可以注冊自己到分發(fā)器,當(dāng)自己關(guān)心的事件到達(dá)時(shí),進(jìn)行響應(yīng)。如果你熟悉設(shè)計(jì)模式的話,會很快想到觀察者模式,例子中的事件分發(fā)器正式使用了此模式。

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)所支持的事件類型。

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è)單元格,可能需要將該單元格所在的行號和列號包裝進(jìn)事件的數(shù)據(jù)。

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ā)生后要做的動作 handle。

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í)例注冊自己,或者發(fā)送事件到此實(shí)例。事件分發(fā)器事實(shí)上需要為何這樣一個(gè)數(shù)據(jù)結(jié)構(gòu):

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ā)器會找到該事件處理器的數(shù)組,然后依次調(diào)用監(jiān)聽器的 handle 方法進(jìn)行相應(yīng)。好了,到此為止,我們已經(jīng)有了事件分發(fā)器的基本框架了,下來,我們開始實(shí)現(xiàn)我們的組件(Component)。

組件要通信,則需要加入事件支持,因此可以抽取出一個(gè)類:

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 事件,也可以注冊監(jiān)聽器,這個(gè) EventSupport 僅僅做了一個(gè)代理,將實(shí)際的工作代理到事件分發(fā)器上。

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:

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):

{items : ["China", "Canada", "U.S.A", "U.K", "Uruguay"]};  

則可以 render 出如下圖所示的 List:

http://wiki.jikexueyuan.com/project/javascript-core/images/js11.png" alt="" />

好,既然組件模型已經(jīng)有了,事件分發(fā)器的框架也有了,相信你已經(jīng)迫不及待的想要看看這些代碼可以干點(diǎn)什么了吧,再耐心一下,我們還要寫一點(diǎn)代碼:

$(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,我們在文檔加載完畢之后,新建了兩個(gè)對象 List 和 Grid,通過點(diǎn)擊 List 上的條目,如果這些條目在 List 的模型上索引發(fā)生變化,則會發(fā)出 EVENT_INDEX_CHAGE 事件,接收到這個(gè)事件的組件或者 DataModel 會做出相應(yīng)的響應(yīng)。在本例中,ldmap 在接收到 EVENT_INDEX_CHANGE 事件后,會組織數(shù)據(jù),并發(fā)出 EVENT_GRID_DATA_READY 事件,而 Grid 接收到這個(gè)事件后,根據(jù)事件對象上綁定的數(shù)據(jù)模型來更新自己的 UI。 上例中的類繼承關(guān)系如下圖:

http://wiki.jikexueyuan.com/project/javascript-core/images/js12.png" alt="" />

圖 事件分發(fā)器類層次

應(yīng)該注意的是,在綁定完監(jiān)聽器之后,我們手動的觸發(fā)了EVENT_LIST_DATA_READY事件,來通知List可以繪制自身了:

uikit.component.EventGenerator.raiseEvent({  
   type : uikit.event.EventTypes.EVENT_LIST_DATA_READY,  
   data : {items : ldmap}  
});  

在實(shí)際的應(yīng)用中,這個(gè)事件可能是用戶在頁面上點(diǎn)擊一個(gè)按鈕,或者一個(gè) Ajax 請求的返回,等等,一旦事件監(jiān)聽器注冊完畢,程序就已經(jīng)就緒,等待異步事件并響應(yīng)。

點(diǎn)擊 List 中的元素 China,Grid 中的數(shù)據(jù)發(fā)生變化

http://wiki.jikexueyuan.com/project/javascript-core/images/js13.png" alt="" />

點(diǎn)擊 Canada,Grid 中的數(shù)據(jù)同樣發(fā)生相應(yīng)的變化:

http://wiki.jikexueyuan.com/project/javascript-core/images/js14.png" alt="" />

由于 List 和 Grid 的數(shù)據(jù)是關(guān)聯(lián)在一起的,他們的數(shù)據(jù)結(jié)構(gòu)具有下列的結(jié)構(gòu):

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)聽多種事件,所以我們可以為 List 的下標(biāo)改變事件注冊另一個(gè)監(jiān)聽器,監(jiān)聽器為一個(gè)簡單組件 Panel,當(dāng)接收到這個(gè)事件后,該 Panel 會根據(jù)一個(gè)隨機(jī)的顏色來重置自身的背景色(注意在 List 和 Grid 下面的灰色 Panel):

http://wiki.jikexueyuan.com/project/javascript-core/images/js15.png" alt="" />

上一篇:客戶端的 JavaScript下一篇:函數(shù)