CONAN ZONE

          你越掙扎我就越興奮

          BlogJava 首頁(yè) 新隨筆 聯(lián)系 聚合 管理
            0 Posts :: 282 Stories :: 0 Comments :: 0 Trackbacks

          簡(jiǎn)介

          這個(gè)關(guān)于 jQuery 的系列文章幫助您提高了創(chuàng)建基于 JavaScript 的 Web 應(yīng)用程序的能力。即使您在閱讀這些文章之前從未聽(tīng)說(shuō)過(guò) jQuery,但是您應(yīng)該已經(jīng)具備了使用 jQuery 構(gòu)建良好的 Web 應(yīng)用程序所需的語(yǔ)言技能和背景知識(shí)。但是,有時(shí)候良好的應(yīng)用程序還不能滿足需求,這時(shí)您將需要一個(gè)強(qiáng)大的 Web 應(yīng)用程序。這就需要通過(guò)幾個(gè)步驟修改現(xiàn)有的應(yīng)用程序,讓它能夠在各種場(chǎng)合中順利運(yùn)行,并且適合所有用戶。這些步驟是優(yōu)化您的 Web 應(yīng)用程序的最后絕招。

          在本文中,我將討論代碼的性能改進(jìn),同時(shí)也談及一些關(guān)于 jQuery 庫(kù)的容易被忽視的問(wèn)題。有些東西對(duì)于復(fù)雜的應(yīng)用程序而言至關(guān)重要,即對(duì)所有應(yīng)用程序都非常關(guān)鍵的插件,以及使應(yīng)用程序的編寫更加容易的良好設(shè)計(jì)技巧。在最 后一個(gè)小節(jié)中,我將討論 jQuery 1.3 中的一些新特性,它們是最近發(fā)布的并且為 jQuery 庫(kù)添加了一些新特性。

          第一個(gè)示例應(yīng)用程序

          本文中的大部分技巧都可以從附帶的樣例應(yīng)用程序中找到(見(jiàn) 下載),這是一個(gè)直觀的電子郵件 Web 應(yīng)用程序。您可能對(duì)它比較熟悉,因?yàn)槲以诒鞠盗械牡谝黄恼轮惺褂玫木褪撬2贿^(guò),您可以看到它是如何從第一篇文章中發(fā)展而來(lái)的,它的性能是如何改進(jìn)的,以及這些最后步驟如何將它轉(zhuǎn)變成強(qiáng)大的 Web 應(yīng)用程序的。


          圖 1. 樣例應(yīng)用程序
          樣例應(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ù)。


          圖 2. Bind/Unbind 示例
          這個(gè)圖表顯示了帶有各種元素的注釋的收件箱片段。Select All/Deselect All 復(fù)選框位于左上角。單獨(dú)的復(fù)選框隨每個(gè)消息行一起運(yùn)行。消息 “You have 5 selected messages” 在每次選擇復(fù)選框時(shí)都更新,對(duì)勾選復(fù)選框的行數(shù)進(jìn)行求和。

          清單 1 顯示了以上設(shè)置的代碼,這是未改進(jìn)之前的原始代碼:


          清單 1. 未經(jīng)過(guò)優(yōu)化的小部件

           1                 
           2 $(document).ready(function(){
           3    // cache this query since it's a search by CLASS
           4    selectable = $(":checked.selectable");
           5    // when the select/deselect all is clicked, do this function
           6    $("#selectall").click(selectAll);
           7    // whenever any individual checkbox is checked, change the text
           8    // describing how many are checked
           9    selectable.click(changeNumFilters);
          10    // calculate how many are initially checked
          11    changeNumFilters();
          12 });
          13 
          14 var selectable;
          15 
          16 function changeNumFilters()
          17 {
          18    // this needs to be checked on every call
          19    // since the length can change with every click
          20    var size = $(":checked.selectable").length;
          21    if (size > 0)
          22       $("#selectedCount").html(size);
          23    else
          24       $("#selectedCount").html("0");
          25  }
          26  
          27 // handles the select/deselect of all checkboxes
          28 function selectAll()
          29 {
          30    var checked = $("#selectall").attr("checked");
          31    selectable.each(function(){
          32       var subChecked = $(this).attr("checked");
          33       if (subChecked != checked)
          34       {
          35          $(this).click();
          36       }
          37    });
          38    changeNumFilters();
          39    }

          該代碼看起來(lái)比較簡(jiǎn)單,因?yàn)槲以诤脦灼恼轮卸加玫竭@個(gè)小部件。您在第一篇文章中見(jiàn)到了 “select/deselect all” 小部件,我給出了它的基礎(chǔ)形式。在關(guān)于性能的文章中,您看到如何通過(guò)緩存選擇查詢和通過(guò) CLASS 減少使用查詢來(lái)改進(jìn)它的性能。但是還有一個(gè)問(wèn)題需要解決。當(dāng)在包含 100 行的表中勾選 “select/deselect all” 復(fù)選框之后,您將得到糟糕的性能。事實(shí)上,在我的瀏覽器中,如果使用了這些代碼,那么完成選擇的平均時(shí)間為 3.4 秒。響應(yīng)性太差了!即使進(jìn)行了各項(xiàng)優(yōu)化,仍然有些不可接受的地方。

          讓我們深入一步考察這個(gè)算法,看看是否有地方出了問(wèn)題。您將遍 歷頁(yè)面上的每個(gè)復(fù)選框,看看它們當(dāng)前的 “checked” 狀態(tài)是否與 “select/deselect all” 復(fù)選框一致。如果不一致,就對(duì)其調(diào)用 “click”,以和 “select/deselect all” 復(fù)選框的狀態(tài)匹配。等一等,您還需要向這些復(fù)選框添加一個(gè)函數(shù),從而在每次單擊時(shí)都調(diào)用 changeNumFilters() 函數(shù)。通過(guò)仔細(xì)檢查,您發(fā)現(xiàn)設(shè)置了一個(gè)可能調(diào)用 changeNumFilters() 101 次的算法。怪不得性能如此差。很明顯,您不需要在每次單擊時(shí)都更新選中的消息的計(jì)數(shù),而是在該過(guò)程完成之后進(jìn)行更新即可。在單擊復(fù)選框的同時(shí)如何才能避免調(diào)用該方法?

          現(xiàn)在,unbind() 方法開(kāi)始發(fā)揮它的作用。通過(guò)在單擊復(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 顯示了更新之后的小部件。


          清單 2. 經(jīng)過(guò)優(yōu)化的小部件

           1 // handles the selection/unselection of all checkboxes
           2 function selectAll()
           3 {
           4    var checked = $("#selectall").attr("checked");
           5    selectable.unbind("click", changeNumFilters);
           6    selectable.each(function(){
           7       var subChecked = $(this).attr("checked");
           8       if (subChecked != checked)
           9       {
          10           $(this).click();
          11       }
          12    });
          13    selectable.bind("click", changeNumFilters);
          14    changeNumFilters();
          15    }

          通過(guò)這些優(yōu)化之后,復(fù)選框的運(yùn)行速度提高到約 900 毫秒,從而大大改進(jìn)了性能。這些改進(jìn)源于返回去檢查您的算法正在做什么,以及貫穿代碼的操作。您可以僅調(diào)用函數(shù) 1 次,而不是 100 次。通過(guò)在本系列的其他文章中不斷改進(jìn)該函數(shù),您最后會(huì)讓它變得更快、更高效。但不一定非得這么做,我還發(fā)現(xiàn)一個(gè)最快的算法,以前從來(lái)沒(méi)有透露過(guò)。此外, 如果我過(guò)早地向您展示這個(gè)最快的算法,我就不能將其作為本文的題材了。希望它能使您看到在代碼中使用 bind/unbind 特性帶來(lái)的好處(如果沒(méi)有更好的方法的話)。

          記?。涸诓幌M|發(fā)默認(rèn)事件時(shí)才使用 bind/unbind,或作為向頁(yè)面元素添加或刪除事件的臨時(shí)方法

          清單 3 顯示了編寫該算法的最快方法(如果您的代碼中有這個(gè)小部件)。它運(yùn)行該函數(shù)僅需 40 毫秒,遠(yuǎn)遠(yuǎn)勝過(guò)之前的其他方法。


          清單 3. 使用超快算法的小部件

          1 function selectAll()
          2 {
          3    var checked = $("#selectall").attr("checked");
          4    selectable.each(function(){
          5       $(this).attr("checked", checked);
          6    });
          7    changeNumFilters();
          8    }

          Live/Die

          jQuery 1.3 版本的另外兩個(gè)強(qiáng)大的新特性是 live()die() 函數(shù)。通過(guò)一個(gè)示例可以看到它對(duì)構(gòu)建設(shè)計(jì)良好的 Web 應(yīng)用程序的作用。想像一下對(duì)表中的每個(gè)單元格都添加一個(gè)雙擊。作為 jQuery 老手,您應(yīng)該知道要在 document.ready() 函數(shù)中設(shè)置雙擊,如清單 4 所示。


          清單 4. 設(shè)置雙擊

          1 $("tr.messageRow").dblclick(function() {
          2    if ($(this).hasClass("mail_unread"))
          3    {
          4       $(this).removeClass("mail_unread");
          5    }
          6    });

          這個(gè)設(shè)計(jì)存在一個(gè)問(wèn)題。它向包含一個(gè) messageRow 類的表的每行添加一個(gè)雙擊事件。但是,如果向該表添加新的行,會(huì)發(fā)生什么事情呢?例如,當(dāng)您使用 Ajax 在未重新加載頁(yè)面的情況下將額外的消息加載到頁(yè)面時(shí),可能會(huì)顯示這些行。這導(dǎo)致一個(gè)問(wèn)題,因?yàn)樗帉懙拇a不能工作。您創(chuàng)建的事件被綁定到所有在加載頁(yè)面 時(shí)顯示的 tr.messageRow 元素中。它沒(méi)有綁定到您在頁(yè)面的生命周期中創(chuàng)建的任何新的 tr.messageRow 中。編寫類似代碼的程序員最終會(huì)很失望的,因?yàn)樗鼰o(wú)法工作。在 jQuery 文檔發(fā)現(xiàn)該問(wèn)題之前,jQuery 新手可能要花好幾個(gè)小時(shí)才能弄明白為什么他們的代碼不能工作(這也是我去年的經(jīng)歷)。

          在 jQuery 1.3 之前,有 3 種可以解決該問(wèn)題的方法,但都不是很好(對(duì)于使用 jQuery 1.2.x 的程序員而言,它們?nèi)匀挥行В5谝环N方法是重新初始化技術(shù),它在每次添加新的元素之后重新將事件添加到選中的元素中。第二種方法利用了 bind/unbind,如前面小節(jié)所示。清單 5 顯示了這兩種方法。

          清單 5. 向新元素添加事件的替代辦法

           1 // first technique to deal with "hot" page elements, added during the page's
           2 // lifetime
           3 $("#mailtable tr #"+message.id).addClass("messageRow")
           4    .dblclick(function() {
           5    if ($(this).hasClass("mail_unread"))
           6    {
           7       $(this).removeClass("mail_unread");
           8    }
           9 
          10 // second technique to deal with "hot" page elements
          11 $("#mailtable tr #"+message.id).addClass("messageRow")
          12    .bind("dblclick", (function() {
          13    if ($(this).hasClass("mail_unread"))
          14    {
          15       $(this).removeClass("mail_unread");
          16       }

          這兩種方法都不是很好。您可能正在重復(fù)編寫代碼,或者被迫查找可能添加新頁(yè)面元素的點(diǎn),然后在這些點(diǎn)上處理 “熱元素” 問(wèn)題。這不是良好的編程方式。但是,jQuery 可能大大簡(jiǎn)化這一切,它能夠幫助我們完成很多事情。

          幸 運(yùn)的是,有一個(gè)插件好像能夠解決該問(wèn)題。這個(gè)插件稱為 LiveQuery 插件,它允許您將特定頁(yè)面元素綁定到事件,但僅能以 “活動(dòng)” 的方式進(jìn)行,因此所有頁(yè)面元素(包括創(chuàng)建頁(yè)面時(shí)自帶的元素和在頁(yè)面的生命周期中創(chuàng)建的元素)都可能觸發(fā)事件。對(duì) UI 開(kāi)發(fā)人員而言,這是一個(gè)非常智能、重要的插件,因?yàn)槭沟锰幚韯?dòng)態(tài)頁(yè)面就像處理靜態(tài)頁(yè)面一樣簡(jiǎn)單。對(duì)于 Web 應(yīng)用程序開(kāi)發(fā)人員而言,它就是真正不可或缺的插件。

          jQuery 核心團(tuán)隊(duì)意識(shí)到該插件的重要性,從而將其包含到 1.3 發(fā)布版中。這個(gè) “活動(dòng)” 特性現(xiàn)在是核心 jQuery 的一部分,因此任何開(kāi)發(fā)人員都可以利用它。這個(gè)特性完整地包含在 1.3 核心代碼中,除了一小部分事件之外。我敢打賭,這些未被包含的事件將出現(xiàn)在 jQuery 的下一個(gè)發(fā)布版之中。我們看看如何利用它改變代碼。


          清單 6. “活動(dòng)” 事件模型
          1 $("tr.messageRow").live("dblclick"function() {
          2    if ($(this).hasClass("mail_unread"))
          3    {
          4       $(this).removeClass("mail_unread");
          5       }


          通過(guò)對(duì)代碼進(jìn)行一處小更改,該頁(yè)面上的所有 tr.messageRow 元素被雙擊時(shí)都將觸發(fā)這段代碼。僅使用 dblclick() 函數(shù)是看不到這種行為的,如上所述。為此,我極力推薦您在大部分事件方法中使用 live() 方法。事實(shí)上,我認(rèn)為它對(duì)于任何動(dòng)態(tài)地創(chuàng)建頁(yè)面元素的頁(yè)面都是必不可少的,不管是通過(guò) 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)性。

          Ajax Queue/Sync

          在 服務(wù)器中使用 Ajax 調(diào)用成為 Web 2.0 公司度量自身的度量指標(biāo)。我們已經(jīng)多次討論過(guò),在 jQuery 中使用 Ajax 就像調(diào)用普通的方法一樣簡(jiǎn)單。這意味著您可以輕松地調(diào)用任何服務(wù)器端 Ajax 函數(shù),就像調(diào)用客戶端的 JavaScript 函數(shù)一樣。但是美中存在一些不足之處,當(dāng)對(duì)服務(wù)器進(jìn)行過(guò)多的 Ajax 調(diào)用時(shí),就會(huì)出現(xiàn)一些負(fù)面效應(yīng)。如果 Web 應(yīng)用程序使用的 Ajax 調(diào)用過(guò)多,就會(huì)導(dǎo)致問(wèn)題。

          第一個(gè)問(wèn)題是一些瀏覽器限制打開(kāi)的服務(wù)器連接的數(shù)量。在 Internet Explorer 中,當(dāng)前版本僅支持打開(kāi) 2 個(gè)服務(wù)器連接。Firefox 支持打開(kāi) 8 個(gè)連接,但仍然是一個(gè)限制。如果 Web 應(yīng)用程序不對(duì) Ajax 調(diào)用進(jìn)行控制,它就很可能打開(kāi) 2 個(gè)以上的連接,尤其是服務(wù)器端調(diào)用屬于時(shí)間密集型調(diào)用時(shí)。這個(gè)問(wèn)題可能源于 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)問(wè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)致問(wèn)題。

          jQuery 的創(chuàng)建者意識(shí)到這個(gè)潛在的問(wèn)題,但同時(shí)也認(rèn)識(shí)到它僅會(huì)給 1% 的 Web 應(yīng)用程序帶來(lái)問(wèn)題。但 1% 開(kāi)發(fā) Web 應(yīng)用程序的開(kāi)發(fā)人員需要一個(gè)解決辦法。因此創(chuàng)建了一個(gè)插件,通過(guò)創(chuàng)建一個(gè) Ajax Queue 和一個(gè) Ajax Sync 來(lái)篩查該問(wèn)題。Queue 和 Sync 的功能是很相似的:Queue 一次發(fā)出一個(gè) Ajax 調(diào)用,并且等待其響應(yīng)返回之后才發(fā)出另一個(gè)調(diào)用。Sync 幾乎同時(shí)發(fā)出多個(gè)調(diào)用,但調(diào)用的響應(yīng)是按先后順序返回的。

          通過(guò)在客戶 端控制 Ajax 調(diào)用解決了超載問(wèn)題,同時(shí)也控制和規(guī)范了將響應(yīng)發(fā)送回客戶端的方式。現(xiàn)在,您可以確保知道響應(yīng)返回到客戶端的順序,從而可以根據(jù)事件的順序編寫代碼。我們 看看這個(gè)插件是如何工作的,以及如何在您的代碼中使用它(見(jiàn)清單 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í)例子,并讓其易于理解是很困難的)。


          清單 7. Ajax Queue
           1 var newRow = "<tr id='?'>" +
           2              "<td><input type=checkbox value='?'></td>" +
           3              "<td>?</td>" +
           4              "<td>?</td>" +
           5              "<td>?</td>" +
           6              "<td>?</td></tr>";
           7    
           8    
           9 $("#mailtable").everyTime(30000"checkForMail"function(){
          10 
          11    // by using the Ajax Queue here, we can be sure that we will check for mail
          12    // every 30 seconds, but ONLY if the previous mail check has already returned.
          13    // This actually would be beneficial in a mail application, if one check for
          14    // new mail takes longer than 30 seconds to respond, we wouldn't want the
          15    // next Ajax call to kick off, because it might duplicate messages (depending
          16    // on the server side code).
          17    // So, by using the Ajax Queue plug-in, we can ensure that our Web client
          18    // is only checking for new mail once, and will never overlap itself.
          19 
          20     $.ajaxQueue({
          21          url: "check_for_mail.jsp",
          22          success: function(data)
          23          {
          24            var message = eval('(' + data + ')');
          25            if (message.id != 0)
          26            {
          27              var row = newRow.replace("?", message.id);
          28              row = row.replace("?", message.id);
          29              row = row.replace("?", message.to);
          30              row = row.replace("?", message.from);
          31              row = row.replace("?", message.subject);
          32              row = row.replace("?", message.sentTime);
          33              $("#mailtable tbody").prepend(row);
          34              $("#mailtable #"+message.id).addClass("mail_unread").addClass("messageRow");
          35              $("#mailtable #"+message.id+ " td").addClass("mail");
          36              $("#mailtable :checkbox").addClass("selectable");
          37             }
          38           }
          39           });

          記?。喝绻膽?yīng)用程序有多個(gè)相互依賴的 Ajax 調(diào)用,那么要考慮使用 Ajax Queue 或 Ajax Sync。

          第二個(gè)示例 Web 應(yīng)用程序

          我將使用另一個(gè)小部件解決本文的最后 3 個(gè)問(wèn)題,并且在深入研究其代碼之前展示和解釋它。這個(gè) 401k 小部件并不陌生,因?yàn)槟呀?jīng)在前面的文章見(jiàn)過(guò)它(參見(jiàn) 參考資料 部分獲取這些文章的鏈接)。不過(guò),這回有個(gè)微妙的不同之處,因?yàn)槲以谕粋€(gè)頁(yè)面上兩次添加了這個(gè)小部件。它被添加到兩個(gè)不同的表中。這將帶來(lái)幾個(gè)有趣的地方。圖 3 顯示了這個(gè)小部件:


          圖 3. 401k 小部件
          這個(gè)屏幕截圖顯示了 401k 小部件的兩個(gè)版本,并且?guī)в姓f(shuō)明。

          在 這個(gè)小部件中,我正在做幾件事情。第一件是計(jì)算文本字段之和并確定它們是否為 100。如果它們的和不為 100,我將向用戶顯示一個(gè)錯(cuò)誤,提示他們沒(méi)有正確使用該小部件。第二,我在每個(gè)選項(xiàng)獲取輸入之后對(duì)選項(xiàng)進(jìn)行排序。通過(guò)這種方式,百分比最高的投資分配將 一直出現(xiàn)在表的頂部。這可以在圖 3 中看到,它按百分比對(duì)選項(xiàng)進(jìn)行排序。最后,為了讓它更酷,我添加了一些條帶。

          用于生產(chǎn)這個(gè)小部件的 HTML 代碼出奇地簡(jiǎn)單。清單 8 詳細(xì)地顯示了這個(gè)小部件。


          清單 8. 生成 401k 小部件的 HTML

           1 <p><table width=300 class="percentSort" cellpadding=0 cellspacing=0>
           2 <tbody>
           3   <tr><td>S&P 500 Index</td>
           4   <td><input type=text> %</td></tr>
           5   <tr><td>Russell 2000 Index</td>
           6   <td><input type=text> %</td></tr>
           7   <tr><td>MSCI International Index</td>
           8   <td><input type=text> %</td></tr>
           9   <tr><td>MSCI Emerging Market Index</td>
          10   <td><input type=text> %</td></tr>
          11   <tr><td>REIT Index</td>
          12   <td><input type=text> %</td></tr>
          13 </tbody>
          14 <tfoot>
          15 </tfoot>
          16 </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)多次聽(tīng)到這種陳詞。在這里我僅添加另一樣?xùn)|西,即 “類修飾”,這是很多插件創(chuàng)作者都使用的??纯辞鍐?8 中的 HTML 代碼,僅通過(guò)將一個(gè) percentSort 類添加到表,您就可以顯著改變表的功能和外觀。這是小部件設(shè)計(jì)的目標(biāo),讓添加和刪除小部件就像向小部件添加類一樣簡(jiǎn)單。

          讓我們遵循我曾用 jQuery 設(shè)置小部件的幾個(gè)步驟。通過(guò)查看這些步驟,您可以看到清單 9 中的設(shè)計(jì)模式是如何出現(xiàn)的。


          清單 9. jQuery 小部件代碼
           1 $(document).ready(function() {
           2 
           3   // the first step is to find all the tables on the page with
           4   // a class of percentSort.  These are all the tables we want to
           5   // convert into our widget.
           6   // After we find them, we need to loop through them and take some
           7   // actions on them
           8   // At the conclusion of this block of code, each table that's going to
           9   // be a percentSort widget will have been transformed
          10 
          11   $("table.percentSort").each(function(i){
          12 
          13      // each table needs a unique ID, for namespace issues (discussed later)
          14      // we can simply create a uniqueID from the loop counter
          15 
          16      $(this).attr("id""percentSort-"+i);
          17 
          18      // within each table, let's highlight every other row in the table, to
          19      // give it that "zebra" look
          20 
          21      $(this).find("tbody > tr").filter(":odd").addClass("highlight");
          22 
          23      // because each table needs to show the "Total" to the user, let's create a new
          24      // section of the table in the footer.  We'll add a row in the table footer
          25      // to display the words "Total" and a span for the updated count.
          26 
          27      $("#"+$(this).attr("id"+ " tfoot")
          28           .append("<tr><td>Total</td><td>
          29           <span></span> %</td></tr>");
          30 
          31      // finally, let's add the CLASS of "percentTotal" to the span we just
          32      // created above.  We'll use this information later to display
          33      // the updated totals
          34 
          35      $("#"+$(this).attr("id"+ " tfoot span").addClass("percentTotal");
          36   });
          37 
          38   // now the second step, after we've completed setting up the tables themselves
          39   // is to set up the individual table rows.
          40   // We can similarly sort through each of them, taking the appropriate actions
          41   // on each of them in turn.
          42   // Upon completion of this block of code, each row in each table will be
          43   // transformed for our widget
          44 
          45   $("table.percentSort tbody > tr").each(function(i){
          46 
          47      // get the namespace (to be discussed in the next section)
          48 
          49      var NAMESPACE = $(this).parents("table.percentSort").attr("id");
          50 
          51      // attach a unique ID to this row.  We can use the loop counter
          52      // to ensure the ID is unique on the page (which is a must on every page)
          53 
          54      $(this).attr("id""row"+i);
          55 
          56      // now, within this row of the table, we need to find the text input, because
          57      // we need to attach a class to them.  We utilize the namespace, and also
          58      // find the :text within the table row, and then attach the correct class
          59 
          60      $("#"+$(this).attr("id"+ " :text").addClass("percent");
          61 
          62      // Finally, we attach a unique ID to each of the text inputs, and we do this by
          63      // making it a combination of the table name and the row name.
          64 
          65      $("#"+$(this).attr("id"+ " .percent").
          66                attr("id", NAMESPACE + "-" + $(this).attr("id"));
          67   });
          68 
          69 
          70   // Finally, because we know we only want numerical inputs, we restrict the text entry
          71   // to just numbers.  We must do this now, because up until this point, the page
          72   // contained no elements with the CLASS "percent"
          73   $(".percent").numeric();

          如您從這個(gè)例子中見(jiàn)到的一樣,可以通過(guò) jQuery 代碼向 HTML 代碼引入大量功能。這種類型的設(shè)計(jì)的好處是很明顯的。同樣,遵循角色分離、代碼重用等也是非常有益的。您還將在小部件插件中看到這種類型的設(shè)計(jì),因?yàn)樗鼘? 簡(jiǎn)單的 HTML 代碼轉(zhuǎn)變成適用于插件的小部件。最重要的是,這也是您在這里需要完成的任務(wù),即編寫一個(gè)插件來(lái)將一個(gè)簡(jiǎn)單的表轉(zhuǎn)變成排序和匯總表。

          記?。罕M量多使用 jQuery 代碼進(jìn)行設(shè)置,并且盡可能少使用 HTML。

          名稱空間

          可 能處理這種類型的設(shè)計(jì)和 jQuery 的最難解方面是理解名稱空間。這個(gè)例子很好,因?yàn)樗砸环N很直觀的方式展示了該問(wèn)題。知道頁(yè)面上的所有 ID 和類時(shí),編寫 jQuery 代碼是非常簡(jiǎn)單的。這就是 jQuery 的設(shè)計(jì)目標(biāo)。當(dāng)您不知道頁(yè)面上有幾個(gè)類時(shí),或這些類開(kāi)始重復(fù)時(shí),會(huì)發(fā)生什么呢?在這個(gè)例子中,您可以直接看到問(wèn)題,有兩個(gè)完全相同的小部件!所有東西都是 重復(fù)的。這似乎是不考慮名稱空間引起的問(wèn)題;您的小部件開(kāi)始彼此干擾,最終導(dǎo)致不能正常工作。


          圖 4. 忽略名稱空間帶來(lái)的問(wèn)題
          顯示大小為 401k 的小部件的兩個(gè)版本,并且?guī)в姓f(shuō)明 “Enter the percentage allocation you wish to place YOUR OWN 401k contributions into”。下面是許多生成投資選項(xiàng)的行,以及后面帶百分號(hào)的文本輸入框。

          造成這個(gè)問(wèn)題的原因是您僅調(diào)用 $(".percentTotal") 等,而忽略了應(yīng)該同步哪個(gè)小部件。因?yàn)橄嗤捻?yè)面上有多個(gè)表,因此該頁(yè)面就有 percentTotal 類的多個(gè)實(shí)例。當(dāng)然,如果頁(yè)面上僅有一個(gè)表,那么您就可以確定它是唯一的。但是隨著頁(yè)面更加高級(jí),以及組件的重用越來(lái)越多,這種一個(gè)表的假設(shè)就不再成立。 有些人會(huì)問(wèn),“在這里使用 ID 不行嗎?” 這不能解決問(wèn)題:您打算給它什么 ID?您不可以使用 “percentTotal”,因?yàn)檫@會(huì)帶來(lái)歧義。也不可以使用 “percentTotal-1”,因?yàn)樗槐硎卷?yè)面上的任何東西。(以上數(shù)字畢竟是任意創(chuàng)建的)。您可以向頁(yè)面包含的表添加一些引用來(lái)解決問(wèn)題,比如 “percentTotal-percentSort1”,但這會(huì)讓問(wèn)題更加復(fù)雜。jQuery 有一個(gè)超級(jí)復(fù)雜但又十分容易使用的選擇語(yǔ)法,從而讓這種混合命名模式變得毫無(wú)必要。為什么要重新創(chuàng)造已有的東西呢?讓我們使用 jQuery 的選擇引擎幫助您解決名稱空間問(wèn)題。

          這個(gè)小部件的核心問(wèn)題是決定操作發(fā)生在哪個(gè)小部件中。當(dāng)向文本框輸入數(shù)字時(shí),您可以問(wèn)自己,jQuery 如何知道進(jìn)入哪個(gè)文本框?您在代碼中的 “百分比” 類添加了一個(gè)事件,并且可以在代碼內(nèi)部使用 $(this) 引用它。這將我們帶入下一個(gè)問(wèn)題:jQuery 如何知道問(wèn)題發(fā)生在哪個(gè)小部件中,從而使您能夠更新正確的 percentTotal 字段?我認(rèn)為這不是一件簡(jiǎn)單的事情。這確實(shí)不簡(jiǎn)單。盡管您的代碼能夠向頁(yè)面上帶有 “百分比” 類的每個(gè)文本框添加一個(gè)事件,但如果忽略了發(fā)生事件的小部件就是不妥當(dāng)?shù)摹?/p>

          這 個(gè)問(wèn)題被歸結(jié)為名稱空間問(wèn)題的原因是,小部件的命名不清晰,容易導(dǎo)致問(wèn)題。要使 jQuery 代碼正常工作,每個(gè)名稱都必須在其自己的空間之內(nèi)明確定義,這就出現(xiàn)了術(shù)語(yǔ)名稱空間。您必須避免編寫重名的代碼,實(shí)現(xiàn)每個(gè)小部件都是自含的。您必須能夠在 相同的頁(yè)面上添加相同小部件的多個(gè)實(shí)例,同時(shí)避免名稱重復(fù)。最重要的是,每個(gè)小部件在頁(yè)面上都是獨(dú)立的。

          沒(méi)有能夠處理名稱空間問(wèn)題的現(xiàn)成方法,因此我將展示我的解決辦法,您可以在自己的代碼中使用我的辦法,或通過(guò)了解問(wèn)題創(chuàng)建更好的解決辦法。我喜歡該代碼的原因是它簡(jiǎn)單易用(只有 1 行),并且能夠讓您在某種程度上控制自己的名稱空間??纯辞鍐?10。


          清單 10. 名稱空間解決辦法
           1 // our event is attached to EVERY input text field with a CLASS of "percent"
           2 // this makes our code look good, but can lead to namespace issues
           3 $("table.percentSort input.percent").keyup(function(){
           4 
           5    // this simple line can establish a namespace for us, by getting the unique
           6    // ID attached to our table (the table is considered the "container" for
           7    // our widget.  It could be anything, depending on your specific code.)
           8    // We pass the CLASS of our widget to the parents() function, because
           9    // that effectively encapsulates the widget
          10 
          11    var NAMESPACE = "#" + $(this).parents("table.percentSort").attr("id");
          12 
          13    // with the namespace established, we can use the jQuery selection
          14    // syntax to use this namespace as our prefix for all of our remaining
          15    // searches.  Notice how the ambiguity is removed for our search
          16    // by CLASS of "percent" and "percentTotal"
          17    // This solves our namespace issues
          18 
          19    var sum = $(NAMESPACE + " input.percent").sum();
          20    var totalField = $(NAMESPACE + " .percentTotal");
          21 

          因此,僅需添加一行代碼,您就能夠封裝小部件,從而避免它的函數(shù)與自身的其他實(shí)例重復(fù)(或者甚至是其他碰巧對(duì) ID 或類使用相同名稱的小部件)。這種類型的代碼編寫方式在插件代碼中很常見(jiàn)。編寫良好的插件應(yīng)該考慮到名稱空間問(wèn)題,而糟糕的插件忽略了這個(gè)問(wèn)題。從這個(gè)例 子中可以看到,在代碼中使用名稱空間也是很簡(jiǎn)單的,并且隨著頁(yè)面越來(lái)越復(fù)雜,它可以給您節(jié)省大量時(shí)間。鑒于這個(gè)原因,我建議您現(xiàn)在就開(kāi)始考慮 jQuery 代碼中的名稱空間問(wèn)題,并在編寫代碼時(shí)使用這種解決辦法。

          記住:開(kāi)始編寫 jQuery 代碼時(shí),一般就要開(kāi)始包含名稱空間解決辦法。您可以使用以上的解決辦法,或創(chuàng)建自己的解決辦法。通過(guò)采用名稱空間解決辦法,即使代碼越來(lái)越復(fù)雜,也不會(huì)帶來(lái)名稱重復(fù)問(wèn)題。

          jQuery 1.3 中的新特性

          在本文的最后小節(jié),我打算過(guò)一遍 jQuery 1.3 中包含的新特性。jQuery 1.3 發(fā)布版在性能上是很出色的,光憑這點(diǎn),您就應(yīng)該將代碼遷移到這個(gè)版本。不過(guò),有幾個(gè)新添加的小特性可以幫助您改善代碼。

          添加到 jQuery 1.3 核心發(fā)布版的第一個(gè)新特性是 live()/die() 函數(shù),我已經(jīng)在本文的前面討論過(guò)它們。這可能是最重要的一個(gè)新特性,因?yàn)樗鼈冎档没ㄒ粋€(gè)小節(jié)去討論。添加到 1.3 核心發(fā)布版的另一個(gè)主要特性是 jQuery.Event 類,它將頁(yè)面上發(fā)生的事件封裝在一個(gè) Object 中。這對(duì)事件密集型應(yīng)用程序尤其有益,因?yàn)樗峁┮粋€(gè)良好自含的對(duì)象,用于傳播關(guān)于事件的所有信息。這些信息包括類型、目標(biāo)、X 和 Y 坐標(biāo),甚至是時(shí)間戳。在 1.3 發(fā)布版之前可能也提供這些信息,但它的歸檔沒(méi)有這么好,并且封裝也沒(méi)有這么好。最后添加到 1.3 發(fā)布版的新特性對(duì)所有開(kāi)發(fā)人員都是透明的,但仍然值得一提。在這個(gè)發(fā)布版中,不再需要仔細(xì)區(qū)分瀏覽器,因?yàn)橛幸粋€(gè)針對(duì)瀏覽器或版本的特殊 if 語(yǔ)句。您可以想象一下處理所有支持的瀏覽器的代碼大拼盤。這在某種程度上會(huì)促使 jQuery 版本衰退,因?yàn)闉g覽器的版本更新太快。相反,它們檢查瀏覽器中的功能,但又不考慮瀏覽器的類型/版本。這意味著即使推出了新的瀏覽器,老瀏覽器退役,仍然 不需要更新 jQuery 的版本。對(duì)于不希望每年更新 jQuery 版本并進(jìn)行相關(guān)測(cè)試的站點(diǎn)而言,這是個(gè)好消息。

          結(jié)束語(yǔ)

          本 文提供一些技巧,幫助您將良好的 jQuery 代碼轉(zhuǎn)變成強(qiáng)大的 jQuery 代碼。jQuery 非常簡(jiǎn)單易用(并且是獨(dú)立的 JavaScript 的巨大改進(jìn)),因此編寫良好的 jQuery 代碼也很容易。大部分開(kāi)發(fā)人員在幾分鐘之內(nèi)編寫完并運(yùn)行良好的 jQuery 代碼。但是良好的代碼和強(qiáng)大的代碼是有區(qū)別的。強(qiáng)大的 jQuery 代碼考慮隨著頁(yè)面越來(lái)越復(fù)雜時(shí)的性能問(wèn)題。強(qiáng)大的 jQuery 代碼能夠考慮到頁(yè)面的未來(lái)方向,而不是僅看到當(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è)面元素越來(lái)越多,這些函數(shù)允許代碼隨著頁(yè)面的增長(zhǎng)而增長(zhǎng),這在以前的發(fā)布版中是無(wú)法實(shí)現(xiàn)的。您希望事件處理像頁(yè)面處理一樣具有動(dòng)態(tài)性。第 三個(gè)新添加的特性是 Ajax Queue/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)致問(wèn)題。每個(gè)頁(yè)面 元素和小部件都應(yīng)該是自含的,不與頁(yè)面的其他方面發(fā)生干擾,名稱空間解決辦法能夠阻止該問(wèn)題。

          這 5 個(gè)步驟并不難實(shí)現(xiàn)。事實(shí)上,其中 4 個(gè)步驟僅需修改一行代碼。不過(guò),理解如何在代碼中應(yīng)用它們才是最重要的。像所有東西一樣,如果不能正確時(shí)使用,它不僅不能提供幫助,反而還有害處。我的建 議是,當(dāng)您用 jQuery 代碼編寫頁(yè)面時(shí),要盡快應(yīng)用這 5 個(gè)步驟。每個(gè)開(kāi)發(fā)人員都會(huì)告訴您,利用特性是編程過(guò)程的一部分。您不希望僅因開(kāi)頭設(shè)計(jì)得不好或更改某些部分而重新設(shè)計(jì)整個(gè) Web 應(yīng)用程序。編寫代碼時(shí)就要抱有編寫強(qiáng)大應(yīng)用程序的想法,并且遵循這些建議。

          最后,這是我的 jQuery 系列文章的第二個(gè)周期的末尾。這最后 5 篇文章將您的 jQuery 水平提升一個(gè)新的級(jí)別,現(xiàn)在您應(yīng)該能夠使用 jQuery 庫(kù)創(chuàng)建任何類型的應(yīng)用程序。我的最后一點(diǎn)建議是經(jīng)常以實(shí)驗(yàn)的方式編寫代碼。您不僅能夠?qū)W到很多東西,還將創(chuàng)建出更酷的 Web 應(yīng)用程序!

          posted on 2009-09-08 15:41 CONAN 閱讀(332) 評(píng)論(0)  編輯  收藏 所屬分類: JQuery
          主站蜘蛛池模板: 张掖市| 谷城县| 襄垣县| 万盛区| 从江县| 抚顺县| 青河县| 蒙城县| 类乌齐县| 邻水| 长武县| 碌曲县| 绍兴县| 微山县| 镇巴县| 日喀则市| 长武县| 恭城| 壤塘县| 富源县| 商水县| 辉县市| 五大连池市| 昭通市| 阜阳市| 连城县| 尤溪县| 余姚市| 唐山市| 定陶县| 乾安县| 崇左市| 张家港市| 镇康县| 洮南市| 沂南县| 隆子县| 崇明县| 南漳县| 宝丰县| 噶尔县|