jQuery高級(jí)應(yīng)用:優(yōu)化Web應(yīng)用程序的最后絕招
jQuery讓編寫基于JavaScript的良好Web應(yīng)用程序變得簡(jiǎn)單明了,但是要想將好的Web應(yīng)用程序變得更出色還需要額外幾個(gè)步驟。本文詳細(xì)闡述幾個(gè)讓W(xué)eb應(yīng)用程序變得更強(qiáng)大的步驟,這些步驟是優(yōu)化您的Web應(yīng)用程序的***絕招。
51CTO推薦專題:jQuery從入門到精通
***個(gè)示例應(yīng)用程序
本文中的大部分技巧都可以從附帶的樣例應(yīng)用程序中找到,這是一個(gè)直觀的電子郵件Web應(yīng)用程序。不過,您可以看到它是如何從***篇文章中發(fā)展而來的,它的性能是如何改進(jìn)的,以及這些***步驟如何將它轉(zhuǎn)變成強(qiáng)大的Web應(yīng)用程序的。
Bind/Unbind
在Events模塊中有兩個(gè)函數(shù),它們是bind()和unbind(),用于完成所有其他事件方法的任務(wù)。如果您能夠向頁(yè)面元素添加一個(gè)click()方法,那么哪還有必要調(diào)用bind("click")呢?這僅是浪費(fèi)時(shí)間而已。但是,這些函數(shù)在特定情況下是非常方便的,如果正確地使用它們,可以顯著提高應(yīng)用程序的性能。
這些函數(shù)不僅能夠向特定頁(yè)面元素添加事件(就像該模塊中的許多其他事件方法一樣),而且還可以從頁(yè)面元素中刪除這些事件。為什么要這樣做?下面我們看看這個(gè)Web應(yīng)用程序,以及如何在特定情況下使用這些函數(shù)。
清單1顯示了以上設(shè)置的代碼,這是未改進(jìn)之前的原始代碼:
- $(document).ready(function(){
- //cachethisquerysinceit'sasearchbyCLASS
- selectable=$(":checked.selectable");
- //whentheselect/deselectallisclicked,dothisfunction
- $("#selectall").click(selectAll);
- //wheneveranyindividualcheckboxischecked,changethetext
- //describinghowmanyarechecked
- selectable.click(changeNumFilters);
- //calculatehowmanyareinitiallychecked
- changeNumFilters();
- });
- varselectable;
- functionchangeNumFilters()
- {
- //thisneedstobecheckedoneverycall
- //sincethelengthcanchangewitheveryclick
- varsize=$(":checked.selectable").length;
- if(size>0)
- $("#selectedCount").html(size);
- else
- $("#selectedCount").html("0");
- }
- //handlestheselect/deselectofallcheckboxes
- functionselectAll()
- {
- varchecked=$("#selectall").attr("checked");
- selectable.each(function(){
- varsubChecked=$(this).attr("checked");
- if(subChecked!=checked)
- {
- $(this).click();
- }
- });
- changeNumFilters();
- }
該代碼看起來比較簡(jiǎn)單,因?yàn)槲以诤脦灼恼轮卸加玫竭@個(gè)小部件。您在***篇文章中見到了“select/deselectall”小部件,我給出了它的基礎(chǔ)形式。在關(guān)于性能的文章中,您看到如何通過緩存選擇查詢和通過CLASS減少使用查詢來改進(jìn)它的性能。但是還有一個(gè)問題需要解決。當(dāng)在包含100行的表中勾選“select/deselectall”復(fù)選框之后,您將得到糟糕的性能。事實(shí)上,在我的瀏覽器中,如果使用了這些代碼,那么完成選擇的平均時(shí)間為3.4秒。響應(yīng)性太差了!即使進(jìn)行了各項(xiàng)優(yōu)化,仍然有些不可接受的地方。
讓我們深入一步考察這個(gè)算法,看看是否有地方出了問題。您將遍歷頁(yè)面上的每個(gè)復(fù)選框,看看它們當(dāng)前的“checked”狀態(tài)是否與“select/deselectall”復(fù)選框一致。如果不一致,就對(duì)其調(diào)用“click”,以和“select/deselectall”復(fù)選框的狀態(tài)匹配。等一等,您還需要向這些復(fù)選框添加一個(gè)函數(shù),從而在每次單擊時(shí)都調(diào)用changeNumFilters()函數(shù)。
通過仔細(xì)檢查,您發(fā)現(xiàn)設(shè)置了一個(gè)可能調(diào)用changeNumFilters()101次的算法。怪不得性能如此差。很明顯,您不需要在每次單擊時(shí)都更新選中的消息的計(jì)數(shù),而是在該過程完成之后進(jìn)行更新即可。在單擊復(fù)選框的同時(shí)如何才能避免調(diào)用該方法?
現(xiàn)在,unbind()方法開始發(fā)揮它的作用。通過在單擊復(fù)選框之前調(diào)用unbind(),將停止調(diào)用click(),同時(shí)避免了click()進(jìn)一步調(diào)用changeNumFilter()方法。這很棒!現(xiàn)在就不會(huì)調(diào)用changeNumFilters()101次了。但是,這僅是一次有效的,在調(diào)用click方法之后,需要使用bind方法將click方法添加回到每個(gè)復(fù)選框。清單2顯示了更新之后的小部件。
- //handlestheselection/unselectionofallcheckboxes
- functionselectAll()
- {
- varchecked=$("#selectall").attr("checked");
- selectable.unbind("click",changeNumFilters);
- selectable.each(function(){
- varsubChecked=$(this).attr("checked");
- if(subChecked!=checked)
- {
- $(this).click();
- }
- });
- selectable.bind("click",changeNumFilters);
- changeNumFilters();
- }
通過這些優(yōu)化之后,復(fù)選框的運(yùn)行速度提高到約900毫秒,從而大大改進(jìn)了性能。這些改進(jìn)源于返回去檢查您的算法正在做什么,以及貫穿代碼的操作。您可以僅調(diào)用函數(shù)1次,而不是100次。通過在本系列的其他文章中不斷改進(jìn)該函數(shù),您***會(huì)讓它變得更快、更高效。但不一定非得這么做,我還發(fā)現(xiàn)一個(gè)最快的算法,以前從來沒有透露過。此外,如果我過早地向您展示這個(gè)最快的算法,我就不能將其作為本文的題材了。希望它能使您看到在代碼中使用bind/unbind特性帶來的好處(如果沒有更好的方法的話)。
記住:在不希望觸發(fā)默認(rèn)事件時(shí)才使用bind/unbind,或作為向頁(yè)面元素添加或刪除事件的臨時(shí)方法
清單3顯示了編寫該算法的最快方法(如果您的代碼中有這個(gè)小部件)。它運(yùn)行該函數(shù)僅需40毫秒,遠(yuǎn)遠(yuǎn)勝過之前的其他方法。
- functionselectAll()
- {
- varchecked=$("#selectall").attr("checked");
- selectable.each(function(){
- $(this).attr("checked",checked);
- });
- changeNumFilters();
- }
#p#
Live/Die
jQuery 1.3版本的另外兩個(gè)強(qiáng)大的新特性是live()和die()函數(shù)。通過一個(gè)示例可以看到它對(duì)構(gòu)建設(shè)計(jì)良好的Web應(yīng)用程序的作用。想像一下對(duì)表中的每個(gè)單元格都添加一個(gè)雙擊。作為jQuery老手,您應(yīng)該知道要在document.ready()函數(shù)中設(shè)置雙擊,如清單4所示。
- $("tr.messageRow").dblclick(function(){
- if($(this).hasClass("mail_unread"))
- {
- $(this).removeClass("mail_unread");
- }
- });
這個(gè)設(shè)計(jì)存在一個(gè)問題。它向包含一個(gè)messageRow類的表的每行添加一個(gè)雙擊事件。但是,如果向該表添加新的行,會(huì)發(fā)生什么事情呢?例如,當(dāng)您使用Ajax在未重新加載頁(yè)面的情況下將額外的消息加載到頁(yè)面時(shí),可能會(huì)顯示這些行。這導(dǎo)致一個(gè)問題,因?yàn)樗帉懙拇a不能工作。您創(chuàng)建的事件被綁定到所有在加載頁(yè)面時(shí)顯示的tr.messageRow元素中。
它沒有綁定到您在頁(yè)面的生命周期中創(chuàng)建的任何新的tr.messageRow中。編寫類似代碼的程序員最終會(huì)很失望的,因?yàn)樗鼰o法工作。在jQuery文檔發(fā)現(xiàn)該問題之前,jQuery新手可能要花好幾個(gè)小時(shí)才能弄明白為什么他們的代碼不能工作(這也是我去年的經(jīng)歷)。
在jQuery 1.3之前,有3種可以解決該問題的方法,但都不是很好(對(duì)于使用jQuery 1.2.x的程序員而言,它們?nèi)匀挥行В?**種方法是重新初始化技術(shù),它在每次添加新的元素之后重新將事件添加到選中的元素中。第二種方法利用了bind/unbind,如前面小節(jié)所示。清單5顯示了這兩種方法。
- //firsttechniquetodealwith"hot"pageelements,addedduringthepage's
- //lifetime
- $("#mailtabletr#"+message.id).addClass("messageRow")
- .dblclick(function(){
- if($(this).hasClass("mail_unread"))
- {
- $(this).removeClass("mail_unread");
- }
- //secondtechniquetodealwith"hot"pageelements
- $("#mailtabletr#"+message.id).addClass("messageRow")
- .bind("dblclick",(function(){
- if($(this).hasClass("mail_unread"))
- {
- $(this).removeClass("mail_unread");
- }
這兩種方法都不是很好。您可能正在重復(fù)編寫代碼,或者被迫查找可能添加新頁(yè)面元素的點(diǎn),然后在這些點(diǎn)上處理“熱元素”問題。這不是良好的編程方式。但是,jQuery可能大大簡(jiǎn)化這一切,它能夠幫助我們完成很多事情。
幸運(yùn)的是,有一個(gè)插件好像能夠解決該問題。這個(gè)插件稱為L(zhǎng)iveQuery插件,它允許您將特定頁(yè)面元素綁定到事件,但僅能以“活動(dòng)”的方式進(jìn)行,因此所有頁(yè)面元素(包括創(chuàng)建頁(yè)面時(shí)自帶的元素和在頁(yè)面的生命周期中創(chuàng)建的元素)都可能觸發(fā)事件。對(duì)UI開發(fā)人員而言,這是一個(gè)非常智能、重要的插件,因?yàn)槭沟锰幚韯?dòng)態(tài)頁(yè)面就像處理靜態(tài)頁(yè)面一樣簡(jiǎn)單。對(duì)于Web應(yīng)用程序開發(fā)人員而言,它就是真正不可或缺的插件。
jQuery核心團(tuán)隊(duì)意識(shí)到該插件的重要性,從而將其包含到1.3發(fā)布版中。這個(gè)“活動(dòng)”特性現(xiàn)在是核心jQuery的一部分,因此任何開發(fā)人員都可以利用它。這個(gè)特性完整地包含在1.3核心代碼中,除了一小部分事件之外。我敢打賭,這些未被包含的事件將出現(xiàn)在jQuery的下一個(gè)發(fā)布版之中。我們看看如何利用它改變代碼。
- $("tr.messageRow").live("dblclick",function(){
- if($(this).hasClass("mail_unread"))
- {
- $(this).removeClass("mail_unread");
- }
通過對(duì)代碼進(jìn)行一處小更改,該頁(yè)面上的所有tr.messageRow元素被雙擊時(shí)都將觸發(fā)這段代碼。僅使用dblclick()函數(shù)是看不到這種行為的,如上所述。為此,我極力推薦您在大部分事件方法中使用live()方法。事實(shí)上,我認(rèn)為它對(duì)于任何動(dòng)態(tài)地創(chuàng)建頁(yè)面元素的頁(yè)面都是必不可少的,不管是通過Ajax還是用戶交互進(jìn)行創(chuàng)建,都需要使用live()函數(shù)而不是事件方法。它很好的實(shí)現(xiàn)了易編寫和bug之間的折衷。
記?。寒?dāng)將事件添加到動(dòng)態(tài)頁(yè)面元素時(shí)要使用live()方法。這讓事件和頁(yè)面元素一樣具有動(dòng)態(tài)性。
#p#
Ajax Queue/Sync
在服務(wù)器中使用Ajax調(diào)用成為Web2.0公司度量自身的度量指標(biāo)。我們已經(jīng)多次討論過,在jQuery中使用Ajax就像調(diào)用普通的方法一樣簡(jiǎn)單。這意味著您可以輕松地調(diào)用任何服務(wù)器端Ajax函數(shù),就像調(diào)用客戶端的JavaScript函數(shù)一樣。但是美中存在一些不足之處,當(dāng)對(duì)服務(wù)器進(jìn)行過多的Ajax調(diào)用時(shí),就會(huì)出現(xiàn)一些負(fù)面效應(yīng)。如果Web應(yīng)用程序使用的Ajax調(diào)用過多,就會(huì)導(dǎo)致問題。
***個(gè)問題是一些瀏覽器限制打開的服務(wù)器連接的數(shù)量。在Internet Explorer中,當(dāng)前版本僅支持打開2個(gè)服務(wù)器連接。Firefox支持打開8個(gè)連接,但仍然是一個(gè)限制。如果Web應(yīng)用程序不對(duì)Ajax調(diào)用進(jìn)行控制,它就很可能打開2個(gè)以上的連接,尤其是服務(wù)器端調(diào)用屬于時(shí)間密集型調(diào)用時(shí)。這個(gè)問題可能源于Web應(yīng)用程序的不良設(shè)計(jì),或用戶不對(duì)請(qǐng)求加以限制。不管是那種情況都是不可取的,因?yàn)槟幌M蔀g覽器決定使用哪些連接。
另外,因?yàn)檎{(diào)用是異步的,不能保證從服務(wù)器返回的響應(yīng)的順序與發(fā)送時(shí)一樣。例如,如果您幾乎同時(shí)發(fā)出2個(gè)Ajax調(diào)用,您就不能保證服務(wù)器的響應(yīng)是以相同的順序返回。因此,如果第二個(gè)調(diào)用依賴于***個(gè)調(diào)用的結(jié)果,那么就會(huì)出現(xiàn)問題。想象這樣一種場(chǎng)景,其中***個(gè)調(diào)用獲取數(shù)據(jù),第二個(gè)調(diào)用在客戶端操作該數(shù)據(jù)。如果第二個(gè)調(diào)用的響應(yīng)返回得比***個(gè)Ajax調(diào)用快,那么您的代碼就會(huì)導(dǎo)致錯(cuò)誤。您完全不能保證響應(yīng)速度。當(dāng)調(diào)用更多時(shí),就更容易導(dǎo)致問題。
jQuery的創(chuàng)建者意識(shí)到這個(gè)潛在的問題,但同時(shí)也認(rèn)識(shí)到它僅會(huì)給1%的Web應(yīng)用程序帶來問題。但1%開發(fā)Web應(yīng)用程序的開發(fā)人員需要一個(gè)解決辦法。因此創(chuàng)建了一個(gè)插件,通過創(chuàng)建一個(gè)Ajax Queue和一個(gè)Ajax Sync來篩查該問題。Queue和Sync的功能是很相似的:Queue一次發(fā)出一個(gè)Ajax調(diào)用,并且等待其響應(yīng)返回之后才發(fā)出另一個(gè)調(diào)用。Sync幾乎同時(shí)發(fā)出多個(gè)調(diào)用,但調(diào)用的響應(yīng)是按先后順序返回的。
通過在客戶端控制Ajax調(diào)用解決了超載問題,同時(shí)也控制和規(guī)范了將響應(yīng)發(fā)送回客戶端的方式?,F(xiàn)在,您可以確保知道響應(yīng)返回到客戶端的順序,從而可以根據(jù)事件的順序編寫代碼。我們看看這個(gè)插件是如何工作的,以及如何在您的代碼中使用它(見清單7)。記住,這僅是為1%的Web應(yīng)用程序設(shè)計(jì)的,它們擁有多個(gè)Ajax調(diào)用,并且后一個(gè)調(diào)用嚴(yán)重依賴于前一個(gè)調(diào)用的結(jié)果。這個(gè)示例不是調(diào)用相互依賴的例子,但它能夠向您展示如何使用該插件(要為這個(gè)插件的應(yīng)用創(chuàng)建一個(gè)***的真實(shí)例子,并讓其易于理解是很困難的)。
- varnewRow="<trid='?'>"+
- "<td><inputtypeinputtype=checkboxvalue='?'></td>"+
- "<td>?</td>"+
- "<td>?</td>"+
- "<td>?</td>"+
- "<td>?</td></tr>";
- $("#mailtable").everyTime(30000,"checkForMail",function(){
- //byusingtheAjaxQueuehere,wecanbesurethatwewillcheckformail
- //every30seconds,butONLYifthepreviousmailcheckhasalreadyreturned.
- //Thisactuallywouldbebeneficialinamailapplication,ifonecheckfor
- //newmailtakeslongerthan30secondstorespond,wewouldn'twantthe
- //nextAjaxcalltokickoff,becauseitmightduplicatemessages(depending
- //ontheserversidecode).
- //So,byusingtheAjaxQueueplug-in,wecanensurethatourWebclient
- //isonlycheckingfornewmailonce,andwillneveroverlapitself.
- $.ajaxQueue({
- url:"check_for_mail.jsp",
- success:function(data)
- {
- varmessage=eval('('+data+')');
- if(message.id!=0)
- {
- varrow=newRow.replace("?",message.id);
- rowrow=row.replace("?",message.id);
- rowrow=row.replace("?",message.to);
- rowrow=row.replace("?",message.from);
- rowrow=row.replace("?",message.subject);
- rowrow=row.replace("?",message.sentTime);
- $("#mailtabletbody").prepend(row);
- $("#mailtable#"+message.id).addClass("mail_unread").addClass("messageRow");
- $("#mailtable#"+message.id+"td").addClass("mail");
- $("#mailtable:checkbox").addClass("selectable");
- }
- }
- });
記?。喝绻膽?yīng)用程序有多個(gè)相互依賴的Ajax調(diào)用,那么要考慮使用Ajax Queue或Ajax Sync。
#p#
第二個(gè)示例Web應(yīng)用程序
我將使用另一個(gè)小部件解決本文的***3個(gè)問題,并且在深入研究其代碼之前展示和解釋它。這個(gè)401k小部件并不陌生,因?yàn)槟呀?jīng)在前面的文章見過它(參見參考資料部分獲取這些文章的鏈接)。不過,這回有個(gè)微妙的不同之處,因?yàn)槲以谕粋€(gè)頁(yè)面上兩次添加了這個(gè)小部件。它被添加到兩個(gè)不同的表中。這將帶來幾個(gè)有趣的地方。圖3顯示了這個(gè)小部件:
在這個(gè)小部件中,我正在做幾件事情。***件是計(jì)算文本字段之和并確定它們是否為100。如果它們的和不為100,我將向用戶顯示一個(gè)錯(cuò)誤,提示他們沒有正確使用該小部件。第二,我在每個(gè)選項(xiàng)獲取輸入之后對(duì)選項(xiàng)進(jìn)行排序。通過這種方式,百分比***的投資分配將一直出現(xiàn)在表的頂部。這可以在圖3中看到,它按百分比對(duì)選項(xiàng)進(jìn)行排序。***,為了讓它更酷,我添加了一些條帶。
用于生產(chǎn)這個(gè)小部件的HTML代碼出奇地簡(jiǎn)單。清單8詳細(xì)地顯示了這個(gè)小部件。
- <p><tablewidthtablewidth=300class="percentSort"cellpadding=0cellspacing=0>
- <tbody>
- <tr><td>S&P500Index</td>
- <td><inputtypeinputtype=text>%</td></tr>
- <tr><td>Russell2000Index</td>
- <td><inputtypeinputtype=text>%</td></tr>
- <tr><td>MSCIInternationalIndex</td>
- <td><inputtypeinputtype=text>%</td></tr>
- <tr><td>MSCIEmergingMarketIndex</td>
- <td><inputtypeinputtype=text>%</td></tr>
- <tr><td>REITIndex</td>
- <td><inputtypeinputtype=text>%</td></tr>
- </tbody>
- <tfoot>
- </tfoot>
- </table>
用jQuery設(shè)置小部件
以上的一小段HTML直接引入了這部分內(nèi)容,本小節(jié)關(guān)注如何在jQuery中設(shè)置小部件,以及所需的所有代碼。要將事件附加到頁(yè)面元素或在特定情況下需要添加類時(shí),通常需要這樣做。有時(shí)候還需要更進(jìn)一步。這些小部件的所有設(shè)置代碼都是jQuery代碼。
我可以提供關(guān)于角色分離的理論,讓HTML設(shè)計(jì)師和JavaScript程序員各自完成自己的工作,但是您們可能已經(jīng)多次聽到這種陳詞。在這里我僅添加另一樣?xùn)|西,即“類修飾”,這是很多插件創(chuàng)作者都使用的??纯辞鍐?中的HTML代碼,僅通過將一個(gè)percentSort類添加到表,您就可以顯著改變表的功能和外觀。這是小部件設(shè)計(jì)的目標(biāo),讓添加和刪除小部件就像向小部件添加類一樣簡(jiǎn)單。
讓我們遵循我曾用jQuery設(shè)置小部件的幾個(gè)步驟。通過查看這些步驟,您可以看到清單9中的設(shè)計(jì)模式是如何出現(xiàn)的。
- $(document).ready(function(){
- //thefirststepistofindallthetablesonthepagewith
- //aclassofpercentSort.Theseareallthetableswewantto
- //convertintoourwidget.
- //Afterwefindthem,weneedtoloopthroughthemandtakesome
- //actionsonthem
- //Attheconclusionofthisblockofcode,eachtablethat'sgoingto
- //beapercentSortwidgetwillhavebeentransformed
- $("table.percentSort").each(function(i){
- //eachtableneedsauniqueID,fornamespaceissues(discussedlater)
- //wecansimplycreateauniqueIDfromtheloopcounter
- $(this).attr("id","percentSort-"+i);
- //withineachtable,let'shighlighteveryotherrowinthetable,to
- //giveitthat"zebra"look
- $(this).find("tbody>tr").filter(":odd").addClass("highlight");
- //becauseeachtableneedstoshowthe"Total"totheuser,let'screateanew
- //sectionofthetableinthefooter.We'lladdarowinthetablefooter
- //todisplaythewords"Total"andaspanfortheupdatedcount.
- $("#"+$(this).attr("id")+"tfoot")
- .append("<tr><td>Total</td><td>
- <span></span>%</td></tr>");
- //finally,let'saddtheCLASSof"percentTotal"tothespanwejust
- //createdabove.We'llusethisinformationlatertodisplay
- //theupdatedtotals
- $("#"+$(this).attr("id")+"tfootspan").addClass("percentTotal");
- });
- //nowthesecondstep,afterwe'vecompletedsettingupthetablesthemselves
- //istosetuptheindividualtablerows.
- //Wecansimilarlysortthrougheachofthem,takingtheappropriateactions
- //oneachoftheminturn.
- //Uponcompletionofthisblockofcode,eachrowineachtablewillbe
- //transformedforourwidget
- $("table.percentSorttbody>tr").each(function(i){
- //getthenamespace(tobediscussedinthenextsection)
- varNAMESPACE=$(this).parents("table.percentSort").attr("id");
- //attachauniqueIDtothisrow.Wecanusetheloopcounter
- //toensuretheIDisuniqueonthepage(whichisamustoneverypage)
- $(this).attr("id","row"+i);
- //now,withinthisrowofthetable,weneedtofindthetextinput,because
- //weneedtoattachaclasstothem.Weutilizethenamespace,andalso
- //findthe:textwithinthetablerow,andthenattachthecorrectclass
- $("#"+$(this).attr("id")+":text").addClass("percent");
- //Finally,weattachauniqueIDtoeachofthetextinputs,andwedothisby
- //makingitacombinationofthetablenameandtherowname.
- $("#"+$(this).attr("id")+".percent").
- attr("id",NAMESPACE+"-"+$(this).attr("id"));
- });
- //Finally,becauseweknowweonlywantnumericalinputs,werestrictthetextentry
- //tojustnumbers.Wemustdothisnow,becauseupuntilthispoint,thepage
- //containednoelementswiththeCLASS"percent"
- $(".percent").numeric();
如您從這個(gè)例子中見到的一樣,可以通過jQuery代碼向HTML代碼引入大量功能。這種類型的設(shè)計(jì)的好處是很明顯的。同樣,遵循角色分離、代碼重用等也是非常有益的。您還將在小部件插件中看到這種類型的設(shè)計(jì),因?yàn)樗鼘⒑?jiǎn)單的HTML代碼轉(zhuǎn)變成適用于插件的小部件。最重要的是,這也是您在這里需要完成的任務(wù),即編寫一個(gè)插件來將一個(gè)簡(jiǎn)單的表轉(zhuǎn)變成排序和匯總表。
記住:盡量多使用jQuery代碼進(jìn)行設(shè)置,并且盡可能少使用HTML。
#p#
名稱空間
可能處理這種類型的設(shè)計(jì)和jQuery的最難解方面是理解名稱空間。這個(gè)例子很好,因?yàn)樗砸环N很直觀的方式展示了該問題。知道頁(yè)面上的所有ID和類時(shí),編寫jQuery代碼是非常簡(jiǎn)單的。這就是jQuery的設(shè)計(jì)目標(biāo)。當(dāng)您不知道頁(yè)面上有幾個(gè)類時(shí),或這些類開始重復(fù)時(shí),會(huì)發(fā)生什么呢?在這個(gè)例子中,您可以直接看到問題,有兩個(gè)完全相同的小部件!所有東西都是重復(fù)的。這似乎是不考慮名稱空間引起的問題;您的小部件開始彼此干擾,最終導(dǎo)致不能正常工作。
造成這個(gè)問題的原因是您僅調(diào)用$(".percentTotal")等,而忽略了應(yīng)該同步哪個(gè)小部件。因?yàn)橄嗤捻?yè)面上有多個(gè)表,因此該頁(yè)面就有percentTotal類的多個(gè)實(shí)例。當(dāng)然,如果頁(yè)面上僅有一個(gè)表,那么您就可以確定它是唯一的。但是隨著頁(yè)面更加高級(jí),以及組件的重用越來越多,這種一個(gè)表的假設(shè)就不再成立。有些人會(huì)問,“在這里使用ID不行嗎?”這不能解決問題:您打算給它什么ID?您不可以使用“percentTotal”,因?yàn)檫@會(huì)帶來歧義。
也不可以使用“percentTotal-1”,因?yàn)樗槐硎卷?yè)面上的任何東西。(以上數(shù)字畢竟是任意創(chuàng)建的)。您可以向頁(yè)面包含的表添加一些引用來解決問題,比如“percentTotal-percentSort1”,但這會(huì)讓問題更加復(fù)雜。jQuery有一個(gè)超級(jí)復(fù)雜但又十分容易使用的選擇語法,從而讓這種混合命名模式變得毫無必要。為什么要重新創(chuàng)造已有的東西呢?讓我們使用jQuery的選擇引擎幫助您解決名稱空間問題。
這個(gè)小部件的核心問題是決定操作發(fā)生在哪個(gè)小部件中。當(dāng)向文本框輸入數(shù)字時(shí),您可以問自己,jQuery如何知道進(jìn)入哪個(gè)文本框?您在代碼中的“百分比”類添加了一個(gè)事件,并且可以在代碼內(nèi)部使用$(this)引用它。這將我們帶入下一個(gè)問題:jQuery如何知道問題發(fā)生在哪個(gè)小部件中,從而使您能夠更新正確的percentTotal字段?我認(rèn)為這不是一件簡(jiǎn)單的事情。這確實(shí)不簡(jiǎn)單。盡管您的代碼能夠向頁(yè)面上帶有“百分比”類的每個(gè)文本框添加一個(gè)事件,但如果忽略了發(fā)生事件的小部件就是不妥當(dāng)?shù)摹?/p>
這個(gè)問題被歸結(jié)為名稱空間問題的原因是,小部件的命名不清晰,容易導(dǎo)致問題。要使jQuery代碼正常工作,每個(gè)名稱都必須在其自己的空間之內(nèi)明確定義,這就出現(xiàn)了術(shù)語名稱空間。您必須避免編寫重名的代碼,實(shí)現(xiàn)每個(gè)小部件都是自含的。您必須能夠在相同的頁(yè)面上添加相同小部件的多個(gè)實(shí)例,同時(shí)避免名稱重復(fù)。最重要的是,每個(gè)小部件在頁(yè)面上都是獨(dú)立的。
沒有能夠處理名稱空間問題的現(xiàn)成方法,因此我將展示我的解決辦法,您可以在自己的代碼中使用我的辦法,或通過了解問題創(chuàng)建更好的解決辦法。我喜歡該代碼的原因是它簡(jiǎn)單易用(只有1行),并且能夠讓您在某種程度上控制自己的名稱空間。看看清單10。
- //oureventisattachedtoEVERYinputtextfieldwithaCLASSof"percent"
- //thismakesourcodelookgood,butcanleadtonamespaceissues
- $("table.percentSortinput.percent").keyup(function(){
- //thissimplelinecanestablishanamespaceforus,bygettingtheunique
- //IDattachedtoourtable(thetableisconsideredthe"container"for
- //ourwidget.Itcouldbeanything,dependingonyourspecificcode.)
- //WepasstheCLASSofourwidgettotheparents()function,because
- //thateffectivelyencapsulatesthewidget
- varNAMESPACE="#"+$(this).parents("table.percentSort").attr("id");
- //withthenamespaceestablished,wecanusethejQueryselection
- //syntaxtousethisnamespaceasourprefixforallofourremaining
- //searches.Noticehowtheambiguityisremovedforoursearch
- //byCLASSof"percent"and"percentTotal"
- //Thissolvesournamespaceissues
- varsum=$(NAMESPACE+"input.percent").sum();
- vartotalField=$(NAMESPACE+".percentTotal");
因此,僅需添加一行代碼,您就能夠封裝小部件,從而避免它的函數(shù)與自身的其他實(shí)例重復(fù)(或者甚至是其他碰巧對(duì)ID或類使用相同名稱的小部件)。這種類型的代碼編寫方式在插件代碼中很常見。編寫良好的插件應(yīng)該考慮到名稱空間問題,而糟糕的插件忽略了這個(gè)問題。從這個(gè)例子中可以看到,在代碼中使用名稱空間也是很簡(jiǎn)單的,并且隨著頁(yè)面越來越復(fù)雜,它可以給您節(jié)省大量時(shí)間。鑒于這個(gè)原因,我建議您現(xiàn)在就開始考慮jQuery代碼中的名稱空間問題,并在編寫代碼時(shí)使用這種解決辦法。
記?。洪_始編寫jQuery代碼時(shí),一般就要開始包含名稱空間解決辦法。您可以使用以上的解決辦法,或創(chuàng)建自己的解決辦法。通過采用名稱空間解決辦法,即使代碼越來越復(fù)雜,也不會(huì)帶來名稱重復(fù)問題。
#p#
結(jié)束語
本文提供一些技巧,幫助您將良好的jQuery代碼轉(zhuǎn)變成強(qiáng)大的jQuery代碼。jQuery非常簡(jiǎn)單易用(并且是獨(dú)立的JavaScript的巨大改進(jìn)),因此編寫良好的jQuery代碼也很容易。大部分開發(fā)人員在幾分鐘之內(nèi)編寫完并運(yùn)行良好的jQuery代碼。但是良好的代碼和強(qiáng)大的代碼是有區(qū)別的。強(qiáng)大的jQuery代碼考慮隨著頁(yè)面越來越復(fù)雜時(shí)的性能問題。強(qiáng)大的jQuery代碼能夠考慮到頁(yè)面的未來方向,而不是僅看到當(dāng)前的位置。強(qiáng)大的jQuery代碼是為最復(fù)雜的應(yīng)用程序設(shè)計(jì)的,然后讓應(yīng)用程序處理輸入的簡(jiǎn)單信息。
本文介紹了5個(gè)概念,幫助您將良好的jQuery代碼轉(zhuǎn)變成強(qiáng)大的jQuery代碼。
***個(gè)概念是使用bind()/unbind()方法。當(dāng)您不希望在頁(yè)面的生命周期內(nèi)將事件添加到代碼時(shí),這些方法對(duì)向頁(yè)面元素添加/移除事件非常有用。這些方法在頁(yè)面包含大量事件時(shí)對(duì)提升性能非常重要,或者用于某些用戶界面中。
第二個(gè)概念是使用1.3中包含的新特性live()/die()。這些函數(shù)允許將事件變成動(dòng)態(tài)的,就像頁(yè)面元素一樣。隨著Web應(yīng)用程序包含的頁(yè)面元素越來越多,這些函數(shù)允許代碼隨著頁(yè)面的增長(zhǎng)而增長(zhǎng),這在以前的發(fā)布版中是無法實(shí)現(xiàn)的。您希望事件處理像頁(yè)面處理一樣具有動(dòng)態(tài)性。
第三個(gè)新添加的特性是AjaxQueue/Sync插件,它用于規(guī)范和控制對(duì)服務(wù)器發(fā)出的Ajax調(diào)用,避免它們超出限度(從客戶端角度看)。當(dāng)Ajax調(diào)用的響應(yīng)返回順序很重要時(shí),該插件也能幫上大忙。
第四個(gè)概念是,盡可能多地用jQuery代碼編寫頁(yè)面設(shè)置代碼。這讓HTML的編寫更加簡(jiǎn)單,并且在設(shè)置頁(yè)面時(shí)能夠獲得更多的控制。
***一個(gè)概念是,在代碼中利用名稱空間解決辦法,避免因小部件的函數(shù)名稱重復(fù)而導(dǎo)致問題。每個(gè)頁(yè)面元素和小部件都應(yīng)該是自含的,不與頁(yè)面的其他方面發(fā)生干擾,名稱空間解決辦法能夠阻止該問題。
這5個(gè)步驟并不難實(shí)現(xiàn)。事實(shí)上,其中4個(gè)步驟僅需修改一行代碼。不過,理解如何在代碼中應(yīng)用它們才是最重要的。像所有東西一樣,如果不能正確時(shí)使用,它不僅不能提供幫助,反而還有害處。我的建議是,當(dāng)您用jQuery代碼編寫頁(yè)面時(shí),要盡快應(yīng)用這5個(gè)步驟。每個(gè)開發(fā)人員都會(huì)告訴您,利用特性是編程過程的一部分。您不希望僅因開頭設(shè)計(jì)得不好或更改某些部分而重新設(shè)計(jì)整個(gè)Web應(yīng)用程序。編寫代碼時(shí)就要抱有編寫強(qiáng)大應(yīng)用程序的想法,并且遵循這些建議。
【編輯推薦】