從神奇的"$"函數開始
"$"函數將在文檔加載完成之後為一個指定的button 綁定事件,這些代碼在單個網頁中工作正常。但是如果我們還有其它的網頁,我們將不得不重復這個過程。
復制代碼 代碼如下:
<a href="javascript:;" id="sayHello">Say Hello</a>
<script type="text/javascript">
//when dom ready, do something.
//bind click event to a button.
$(function(){
$('#sayHello').click(function(){
alert('Hello world!');
});
});
</script>
如果我們需要另一個行為的button怎麼辦?比如象這樣:
復制代碼 代碼如下:
<a href="javascript:;" id="sayUnlike">Unlike it</a>
<script type="text/javascript">
//when dom ready, do something.
//bind click event to a button.
$(function(){
$('#sayUnlike').click(function(){
alert('I unlike it.');
});
});
</script>
接下來,更多的問題出現了,我們需要很多這樣的button, 這好象也不難。
復制代碼 代碼如下:
<a href="javascript:;" class="sayUnlike">Unlike it</a>
<script type="text/javascript">
//Change to a class selector to match all the button elements.
$(function(){
$('.sayUnlike').click(function(){
alert('I unlike it.');
});
});
</script>
一個頁面裡面同種出現了兩種button ......
復制代碼 代碼如下:
<a href="javascript:;" class='sayHello'>Say Hello</a>
<a href="javascript:;" class="sayUnlike">Unlike it</a>
<script type="text/javascript">
$(function(){
$('.sayHello').click(function(){
alert('Hello world!');
});
$('.sayUnlike').click(function(){
alert('I unlike it.');
});
});
</script>
但是呢,不是所有的頁面都會用到這兩種的button,為了不在頁面上使用額外的選擇器,我們要作一些必要的調整,因為基於class的選擇器的性能相對於id選擇器開銷很大,需要遍歷所有dom元素,並使用正則表達式匹配class屬性來選定滿足條件的元素。
復制代碼 代碼如下:
<? if($page == 'A'){?>
<script type="text/javascript">
$(function(){
$('.sayHello').click(function(){
alert('Hello world!');
});
});
</script>
<? } ?>
<? if($page == 'B'){?>
<script type="text/javascript">
$(function(){
$('.sayUnlike').click(function(){
alert('I unlike it.');
});
});
</script>
<? } ?>
我們的項目功能越來越復雜,經過一段時間以後,變成了這個樣子, quick but dirty......
復制代碼 代碼如下:
<? if($page == 'A' or $page == "C" and $page is not "D"){ ?>
<script type="text/javascript">
......
</script>
<? } ?>
<? if($page == "B" or $page == "E" and $page is not "X"){ ?>
<script type="text/javascript">
.....
</script>
<? } ?>
<? if($page == "B" or $page == "E" or $page == "C"){ ?>
<script type="text/javascript">
.....
</script>
<? } ?>
這真是太糟糕了,我們需要在一個頁面上加載許多個代碼片斷才能綁定所有的事件,如果我們再將不同的代碼分裝入多個js文件中這將增加多個頁面資源的http請求,不論是管理還是用戶體驗都將面臨挑戰,我們需要找到一個更佳的解決方案。
既然 class selector 的開銷這麼大,我們能不能在一次掃描中綁定所有的事件?我們可以嘗試一下:
復制代碼 代碼如下:
<script type="text/javascript">
//Register global name space.
var Yottaa = Yottaa || {};
Yottaa.EventMonitor = function(){
this.listeners = {};
}
//Bind all event.
Yottaa.EventMonitor.prototype.subscribe=function(msg, callback){
var lst = this.listeners[msg];
if (lst) {
lst.push(callback);
} else {
this.listeners[msg] = [callback];
}
}
// Create the event monitor instance.
var event_monitor = new Yottaa.EventMonitor();
function load_event_monitor(root){
var re = /a_(\w+)/; //using a regular expression to filter all event object.
var fns = {};
$(".j", root).each(function(i) {
var m = re.exec(this.className);
if (m) {
var f = fns[m[1]];
if (!f) { //如果事件處理函數不存在則創建函數對象.
f = eval("Yottaa.init_"+m[1]);
fns[m[1]] = f;//調用綁定函數.
}
f && f(this);
}
});
}
$(function(){
// when dom ready, bind all event.
load_event_monitor(document);
});
//Here is 2 sample components.
Yottaa.init_sayhello = function(obj){
$(obj).click(function(){
alert('Hello world!');
});
}
Yottaa.init_unlike = function(obj){
$(obj).click(function(){
alert('I unlike it.');
});
}
</script>
我們的DOM元素這樣寫:
<a href="javascript:;" class="j a_sayhello">Say Hello</a>
<a href="javascript:;" class="j a_unlike">Say Unlike</a>
這樣看起似乎好多了,我們只需要在頁面加載的時候執行一次class selector(在上面的代碼中就是所有'.j'的元素)就可以找到所有需要綁定事件的元素,具體綁定哪一個組件由 class 名稱裡面的 a_xxx 來決定,對應著 Yottaa.init_xxx,並將當前元素的引用作為參數傳入事件邏輯中。
在這個處理模式下,我們不需要再次手動編寫事件處理的邏輯並將它放到 $(function(){ .... }); 這樣的初始化函數中,所有我們要做的事情僅僅是給組件的“容器”加上兩個 class: "j a_XXX"程序即可幫我完成事件綁定工作,是不是很 cool ?象常用的展開/折疊效果,全選/反選效果, tab切換以致於一些其它的簡單功能都可以使用這種方式。難道這就是傳說中的銀彈?不,事情沒那麼簡單,我們應該看到這種處理方式一些弱點:
不能給組件傳遞初始化參數。
不能體現出組件的包含關系,也不能利用繼承和多態等面向對象的特性使程序更容易編寫和理解。
對於部分具體關聯關系的組件在處理上略顯麻煩,沒有合理的事件通知機制。
我們來看看第一條:關於參數的傳遞,在許多場景下對於多個條目的列表,對應每一個條目我們一般會給元素分配一個唯一一的id,這些元素的行為類似,不同之處只是服務器端的編號不同,比如一個留言列表或者是一個產口列表。我們可以利用id屬性為我們作一些事情,看下面的代碼,我們用id屬性把條目對應的服務器端編號告訴javascript,並在接下來的事件邏輯處理中作為服務器回調函數參數的一部分發回服務器端。
復制代碼 代碼如下:
<script type="text/javascript">
Yottaa.init_sampleajax = function(obj){
$(obj).click(function(){
var component_id = $(this).attr('id').split('-')[1];
$.get('/server/controller/method', {id: component_id}, function(data){
if(data){
alert('Message from server: ' + data );
}
});
});
}
</script>
<a href="javascript:;" class='j a_sampleajax' id='item-a'>Show server message. </a>
<a href="javascript:;" class='j a_sampleajax' id="item-b">Another button with same action but different server side identifier.</a>
在更復雜的一些場景中我們可以利用頁面上的inline code給組件傳遞一些必要的信息。
復制代碼 代碼如下:
Yottaa.globalConst = {
User:{
familyName: "Jhone",
givenName: 'bruce'
},
Url:{
siteName: 'yottaa.com',
score: 98
}
}
Yottaa.componentMetaData = {
compoment_id_1:{ ...... },
component_id_2:{ ...... }
};
上面討論了一種可能的代碼組織辦法,但是並非適用於所有的項目,我們要做的是:針對於目前的現狀,找到一個在代價比較小的重構方案。我們考慮如下幾點:
分離元素的事件綁定代碼和組件代碼:組件代碼包括jquery庫,相關擴展插件,以及我們自己編寫的小部件,如chartbox等內容。
事件綁定及處理邏輯:按不同的組件劃分為多個模塊,每個模塊放入一個function中。
頁面需要指定哪些模塊要在本頁面上初始化,提供一個列表交由全局的事件綁定器統一處理。
下面來演示一下部分代碼:
復制代碼 代碼如下:
<script type="text/javascript">
function init_loginPanel = function(){
var container = $('login_panel');
$('#login_button').click(function(){
......
});
}
function init_chart = function(){
......
}
//global static init method
Yottaa.initComponents = function(components){
for(var i = 0;i<components.length;i++){
if(typeof window[components[i]] == 'Function'){
window[components[i]]();
}
}
}
// above is in the 'all-in-one' assets file which is compressed to one file in production.
var components = ['init_loginPanel', 'init_chart'];
var metaData = {
loginPanel: {},
chart: {},
......
};
$(function(){
Yottaa.initComponents(components);
});
//here is inline script on the page.
</script>