您的位置:知识库 » Web前端

jQuery高级应用:优化Web应用程序的最后绝招

作者: Michael Abernethy  发布时间: 2010-10-28 15:42  阅读: 2649 次  推荐: 0   原文链接   [收藏]  
摘要:在本文中,我将讨论代码的性能改进,同时也谈及一些关于jQuery库的容易被忽视的问题。有些东西对于复杂的应用程序而言至关重要,即对所有应用程序都非常关键的插件,以及使应用程序的编写更加容易的良好设计技巧。
[1] jQuery高级应用:优化Web应用程序的最后绝招
[2] jQuery高级应用:优化Web应用程序的最后绝招

  jQuery让编写基于JavaScript的良好Web应用程序变得简单明了,但是要想将好的Web应用程序变得更出色还需要额外几个步骤。本文详细阐述几个让Web应用程序变得更强大的步骤,这些步骤是优化您的Web应用程序的最后绝招。

  第一个示例应用程序

第一个示例应用程序

  本文中的大部分技巧都可以从附带的样例应用程序中找到,这是一个直观的电子邮件Web应用程序。不过,您可以看到它是如何从第一篇文章中发展而来的,它的性能是如何改进的,以及这些最后步骤如何将它转变成强大的Web应用程序的。

  Bind/Unbind

  在Events模块中有两个函数,它们是bind()和unbind(),用于完成所有其他事件方法的任务。如果您能够向页面元素添加一个click()方法,那么哪还有必要调用bind("click")呢?这仅是浪费时间而已。但是,这些函数在特定情况下是非常方便的,如果正确地使用它们,可以显著提高应用程序的性能。

Bind/Unbind

  这些函数不仅能够向特定页面元素添加事件(就像该模块中的许多其他事件方法一样),而且还可以从页面元素中删除这些事件。为什么要这样做?下面我们看看这个Web应用程序,以及如何在特定情况下使用这些函数。

清单1显示了以上设置的代码,这是未改进之前的原始代码:

1. $(document).ready(function(){
2. //cachethisquerysinceit'sasearchbyCLASS
3. selectable=$(":checked.selectable");
4. //whentheselect/deselectallisclicked,dothisfunction
5. $("#selectall").click(selectAll);
6. //wheneveranyindividualcheckboxischecked,changethetext
7. //describinghowmanyarechecked
8. selectable.click(changeNumFilters);
9. //calculatehowmanyareinitiallychecked
10. changeNumFilters();
11. });
12.
13. varselectable;
14. functionchangeNumFilters()
15. {
16. //thisneedstobecheckedoneverycall
17. //sincethelengthcanchangewitheveryclick
18. varsize=$(":checked.selectable").length;
19. if(size>0)
20. $("#selectedCount").html(size);
21. else
22. $("#selectedCount").html("0");
23. }
24.
25. //handlestheselect/deselectofallcheckboxes
26. functionselectAll()
27. {
28. varchecked=$("#selectall").attr("checked");
29. selectable.each(function(){
30. varsubChecked=$(this).attr("checked");
31. if(subChecked!=checked)
32. {
33. $(this).click();
34. }
35. });
36. changeNumFilters();
37. }

  该代码看起来比较简单,因为我在好几篇文章中都用到这个小部件。您在第一篇文章中见到了“select/deselectall”小部件,我给出了它的基础形式。在关于性能的文章中,您看到如何通过缓存选择查询和通过CLASS减少使用查询来改进它的性能。但是还有一个问题需要解决。当在包含100行的表中勾选“select/deselectall”复选框之后,您将得到糟糕的性能。事实上,在我的浏览器中,如果使用了这些代码,那么完成选择的平均时间为3.4秒。响应性太差了!即使进行了各项优化,仍然有些不可接受的地方。

  让我们深入一步考察这个算法,看看是否有地方出了问题。您将遍历页面上的每个复选框,看看它们当前的“checked”状态是否与“select/deselectall”复选框一致。如果不一致,就对其调用“click”,以和“select/deselectall”复选框的状态匹配。等一等,您还需要向这些复选框添加一个函数,从而在每次单击时都调用changeNumFilters()函数。

  通过仔细检查,您发现设置了一个可能调用changeNumFilters()101次的算法。怪不得性能如此差。很明显,您不需要在每次单击时都更新选中的消息的计数,而是在该过程完成之后进行更新即可。在单击复选框的同时如何才能避免调用该方法?

  现在,unbind()方法开始发挥它的作用。通过在单击复选框之前调用unbind(),将停止调用click(),同时避免了click()进一步调用changeNumFilter()方法。这很棒!现在就不会调用changeNumFilters()101次了。但是,这仅是一次有效的,在调用click方法之后,需要使用bind方法将click方法添加回到每个复选框。清单2显示了更新之后的小部件。

1. //handlestheselection/unselectionofallcheckboxes
2. functionselectAll()
3. {
4. varchecked=$("#selectall").attr("checked");
5. selectable.unbind("click",changeNumFilters);
6. selectable.each(function(){
7. varsubChecked=$(this).attr("checked");
8. if(subChecked!=checked)
9. {
10. $(this).click();
11. }
12. });
13. selectable.bind("click",changeNumFilters);
14. changeNumFilters();
15. }

  通过这些优化之后,复选框的运行速度提高到约900毫秒,从而大大改进了性能。这些改进源于返回去检查您的算法正在做什么,以及贯穿代码的操作。您可以仅调用函数1次,而不是100次。通过在本系列的其他文章中不断改进该函数,您最后会让它变得更快、更高效。但不一定非得这么做,我还发现一个最快的算法,以前从来没有透露过。此外,如果我过早地向您展示这个最快的算法,我就不能将其作为本文的题材了。希望它能使您看到在代码中使用bind/unbind特性带来的好处(如果没有更好的方法的话)。

  记住:在不希望触发默认事件时才使用bind/unbind,或作为向页面元素添加或删除事件的临时方法

  清单3显示了编写该算法的最快方法(如果您的代码中有这个小部件)。它运行该函数仅需40毫秒,远远胜过之前的其他方法。

functionselectAll()
{
varchecked
=$("#selectall").attr("checked");
selectable.each(function(){
$(
this).attr("checked",checked);
});
changeNumFilters();
}

  Live/Die

  jQuery 1.3版本的另外两个强大的新特性是live()和die()函数。通过一个示例可以看到它对构建设计良好的Web应用程序的作用。想像一下对表中的每个单元格都添加一个双击。作为jQuery老手,您应该知道要在document.ready()函数中设置双击,如清单4所示。

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

  这个设计存在一个问题。它向包含一个messageRow类的表的每行添加一个双击事件。但是,如果向该表添加新的行,会发生什么事情呢?例如,当您使用Ajax在未重新加载页面的情况下将额外的消息加载到页面时,可能会显示这些行。这导致一个问题,因为所编写的代码不能工作。您创建的事件被绑定到所有在加载页面时显示的tr.messageRow元素中。

  它没有绑定到您在页面的生命周期中创建的任何新的tr.messageRow中。编写类似代码的程序员最终会很失望的,因为它无法工作。在jQuery文档发现该问题之前,jQuery新手可能要花好几个小时才能弄明白为什么他们的代码不能工作(这也是我去年的经历)。

  在jQuery 1.3之前,有3种可以解决该问题的方法,但都不是很好(对于使用jQuery 1.2.x的程序员而言,它们仍然有效)。第一种方法是重新初始化技术,它在每次添加新的元素之后重新将事件添加到选中的元素中。第二种方法利用了bind/unbind,如前面小节所示。清单5显示了这两种方法。

1. //firsttechniquetodealwith"hot"pageelements,addedduringthepage's
2. //lifetime
3. $("#mailtabletr#"+message.id).addClass("messageRow")
4. .dblclick(function(){
5. if($(this).hasClass("mail_unread"))
6. {
7. $(this).removeClass("mail_unread");
8. }
9.
10. //secondtechniquetodealwith"hot"pageelements
11. $("#mailtabletr#"+message.id).addClass("messageRow")
12. .bind("dblclick",(function(){
13. if($(this).hasClass("mail_unread"))
14. {
15. $(this).removeClass("mail_unread");
16. }

  这两种方法都不是很好。您可能正在重复编写代码,或者被迫查找可能添加新页面元素的点,然后在这些点上处理“热元素”问题。这不是良好的编程方式。但是,jQuery可能大大简化这一切,它能够帮助我们完成很多事情。

  幸运的是,有一个插件好像能够解决该问题。这个插件称为LiveQuery插件,它允许您将特定页面元素绑定到事件,但仅能以“活动”的方式进行,因此所有页面元素(包括创建页面时自带的元素和在页面的生命周期中创建的元素)都可能触发事件。对UI开发人员而言,这是一个非常智能、重要的插件,因为使得处理动态页面就像处理静态页面一样简单。对于Web应用程序开发人员而言,它就是真正不可或缺的插件。

  jQuery核心团队意识到该插件的重要性,从而将其包含到1.3发布版中。这个“活动”特性现在是核心jQuery的一部分,因此任何开发人员都可以利用它。这个特性完整地包含在1.3核心代码中,除了一小部分事件之外。我敢打赌,这些未被包含的事件将出现在jQuery的下一个发布版之中。我们看看如何利用它改变代码。

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

  通过对代码进行一处小更改,该页面上的所有tr.messageRow元素被双击时都将触发这段代码。仅使用dblclick()函数是看不到这种行为的,如上所述。为此,我极力推荐您在大部分事件方法中使用live()方法。事实上,我认为它对于任何动态地创建页面元素的页面都是必不可少的,不管是通过Ajax还是用户交互进行创建,都需要使用live()函数而不是事件方法。它很好的实现了易编写和bug之间的折衷。

  记住:当将事件添加到动态页面元素时要使用live()方法。这让事件和页面元素一样具有动态性。

  Ajax Queue/Sync

  在服务器中使用Ajax调用成为Web2.0公司度量自身的度量指标。我们已经多次讨论过,在jQuery中使用Ajax就像调用普通的方法一样简单。这意味着您可以轻松地调用任何服务器端Ajax函数,就像调用客户端的JavaScript函数一样。但是美中存在一些不足之处,当对服务器进行过多的Ajax调用时,就会出现一些负面效应。如果Web应用程序使用的Ajax调用过多,就会导致问题。

  第一个问题是一些浏览器限制打开的服务器连接的数量。在Internet Explorer中,当前版本仅支持打开2个服务器连接。Firefox支持打开8个连接,但仍然是一个限制。如果Web应用程序不对Ajax调用进行控制,它就很可能打开2个以上的连接,尤其是服务器端调用属于时间密集型调用时。这个问题可能源于Web应用程序的不良设计,或用户不对请求加以限制。不管是那种情况都是不可取的,因为您不希望由浏览器决定使用哪些连接。

  另外,因为调用是异步的,不能保证从服务器返回的响应的顺序与发送时一样。例如,如果您几乎同时发出2个Ajax调用,您就不能保证服务器的响应是以相同的顺序返回。因此,如果第二个调用依赖于第一个调用的结果,那么就会出现问题。想象这样一种场景,其中第一个调用获取数据,第二个调用在客户端操作该数据。如果第二个调用的响应返回得比第一个Ajax调用快,那么您的代码就会导致错误。您完全不能保证响应速度。当调用更多时,就更容易导致问题。

  jQuery的创建者意识到这个潜在的问题,但同时也认识到它仅会给1%的Web应用程序带来问题。但1%开发Web应用程序的开发人员需要一个解决办法。因此创建了一个插件,通过创建一个Ajax Queue和一个Ajax Sync来筛查该问题。Queue和Sync的功能是很相似的:Queue一次发出一个Ajax调用,并且等待其响应返回之后才发出另一个调用。Sync几乎同时发出多个调用,但调用的响应是按先后顺序返回的。

  通过在客户端控制Ajax调用解决了超载问题,同时也控制和规范了将响应发送回客户端的方式。现在,您可以确保知道响应返回到客户端的顺序,从而可以根据事件的顺序编写代码。我们看看这个插件是如何工作的,以及如何在您的代码中使用它(见清单7)。记住,这仅是为1%的Web应用程序设计的,它们拥有多个Ajax调用,并且后一个调用严重依赖于前一个调用的结果。这个示例不是调用相互依赖的例子,但它能够向您展示如何使用该插件(要为这个插件的应用创建一个完美的真实例子,并让其易于理解是很困难的)。

1. varnewRow="<trid='?'>"+
2. "<td><inputtypeinputtype=checkboxvalue='?'></td>"+
3. "<td>?</td>"+
4. "<td>?</td>"+
5. "<td>?</td>"+
6. "<td>?</td></tr>";
1. $("#mailtable").everyTime(30000,"checkForMail",function(){
2. //byusingtheAjaxQueuehere,wecanbesurethatwewillcheckformail
3. //every30seconds,butONLYifthepreviousmailcheckhasalreadyreturned.
4. //Thisactuallywouldbebeneficialinamailapplication,ifonecheckfor
5. //newmailtakeslongerthan30secondstorespond,wewouldn'twantthe
6. //nextAjaxcalltokickoff,becauseitmightduplicatemessages(depending
7. //ontheserversidecode).
8. //So,byusingtheAjaxQueueplug-in,wecanensurethatourWebclient
9. //isonlycheckingfornewmailonce,andwillneveroverlapitself.
10.
11. $.ajaxQueue({
12. url:"check_for_mail.jsp",
13. success:function(data)
14. {
15. varmessage=eval('('+data+')');
16. if(message.id!=0)
17. {
18. varrow=newRow.replace("?",message.id);
19. rowrow=row.replace("?",message.id);
20. rowrow=row.replace("?",message.to);
21. rowrow=row.replace("?",message.from);
22. rowrow=row.replace("?",message.subject);
23. rowrow=row.replace("?",message.sentTime);
24. $("#mailtabletbody").prepend(row);
25. $("#mailtable#"+message.id).addClass("mail_unread").addClass("messageRow");
26. $("#mailtable#"+message.id+"td").addClass("mail");
27. $("#mailtable:checkbox").addClass("selectable");
28. }
29. }
30. });

  记住:如果您的应用程序有多个相互依赖的Ajax调用,那么要考虑使用Ajax Queue或Ajax Sync。

[第1页][第2页]
0
0
标签:jQuery Web

Web前端热门文章

    Web前端最新文章

      最新新闻

        热门新闻