jQuery性能指标和调优
本文的写作灵感源自读者关于先前一篇文章的电子邮件反馈;该邮件这样写道 “jQuery 在简单的页面上无可挑剔,但在复杂的页面上性能极其低下。在解决性能问题之前,您必须对复杂页面使用常规的 JavaScript。” 这立即让我想到 “jQuery 和 JavaScript 之间的性能比较会是什么样的?”事实上,我很少看到将 jQuery 库与 JavaScript 或其他 JavaScript 库进行比较的文章。我可能像大多数人一样,仅看到用 jQuery 编写客户端代码的简易,而忽略了可能的性能问题。在我看来,在易用性和性能之间进行折衷是值得的。但是,存在这样一种折衷吗?jQuery 是不是比 “一般” 的 JavaScript 慢?jQuery 是不是比其他库慢?本文将尝试解答这些问题。
51CTO推荐专题:jQuery从入门到精通
度量 JavaScript 性能要考虑的最重要问题是执行 JavaScript 的环境。因为代码是运行在客户端上的,所以它的执行依赖于客户端计算机,这使得客户端机器成为影响性能的最重要因素。很明显,运行 4 核 CPU 的新服务器执行代码的速度肯定要比 400 MHz 老式处理器快。这是毫无疑问的。不过,由于您不能控制 Web 应用程序用户用于访问您的站点的机器,所以在度量性能时要考虑关于硬件的许多因素。
JavaScript 性能的另一个重要方面是用于运行 JavaScript 的浏览器,JavaScript 新手可能不容易发觉这个影响因素。每个浏览器内部都包含一个 JavaScript 引擎,即用于解析和执行 JavaScript 代码并处理 Web 应用程序页面的本机代码。因此,JavaScript 的性能严重依赖于客户端使用的浏览器,并且在某些情况下,您可以 控制用户使用的浏览器。本文提供一些关于 JavaScript 性能的指导原则,并解释不同浏览器对性能的影响。
最后,在总结 JavaScript,尤其是 jQuery 的性能之后,我将确定一些能够改进 jQuery 代码性能的最佳实践,充分利用运行得最快的代码部分,而尽量避免运行得最慢的代码部分。在您阅读完本文之后,“jQuery 的性能好吗” 这个问题将得到解答,并且给我发送那封电子邮件的人也将知道他的断言是否正确。
创建性能测试
关于性能测试的第一步是创建一个合适的性能测试。jQuery 以及其他 JavaScript 库在代码中扮演的最重要角色就是使用选择查找特定页面元素。我在最初的性能测试中就以这方面为重点。一个良好的性能测试应该真正地发挥 JavaScript 库的全部力量,用包含数千个页面元素的页面测试它。应该运行所有选择方法,让我看到哪个选择方法最快,哪个最慢。测试应该事先知道正确的答案,从而确定 JavaScript 库是否正确地执行选择方法。最后,应该显示所有结果,并附带所用的运行时间,让我能够在所有库之间进行比较。
我差点忽略了性能测试的最重要方面:它应该是免费的。毕竟这个系列文章的不成文规则就是相互利用彼此的成果,因此我继续发扬这种精神,在此使用一个现成的 JavaScript 库性能测试。这个测试称为 SlickSpeed Selectors Test(见 参考资料),它非常适合我的需求。它将 jQuery 1.2.6(撰写本文时的最新版本)与其他 4 个流行的 JavaScript 库(MooTools、Prototype、YUI 和 Dojo)进行比较。然后,它使用带有数千个页面元素的页面运行40个选择测试。换句话说,这是我所希望的最佳性能测试。我将在第一个性能测试分析中使用该测试。
对比JavaScript 库的性能
对于第一个性能测试,我使用的运行环境是 2.2 GHz 处理器、2 GB RAM 和 Firefox 3.0.3(非常重要)。我在该配置下运行 5 次测试,图 1 显示了 5 次运行的平均结果。
图 1. 性能测试 1 的结果
从第一次测试能够得出什么结论?现在我们仅关注总体结果,而不是每次测试。在获得一些总体结论之后,我将稍后在本文中关注每个测试。
◆ 结论 1:YUI 慢到了极点!
对,与其他库相比,YUI 真的很慢。仔细查看每个测试,找出为什么这个库在选择元素组(例如 “p, a”)时非常慢。对于要求具有很好性能的页面而言,这个库是最差的选择。
◆ 结论 2:Mootools、jQuery 和 Dojo 的运行时间几乎一样。
与其他两个库相比,这 3 个库是非常快的,并且 Dojo 是它们当中最快的,而 jQuery 是最慢的。但是从全局考虑,它们之间的速度是很接近的。
◆ 结论 3:这些库之间的相对差别还是比较明显的。
度量最快时间/最慢时间以确定速度的相对差别,您可以看到相对差别为 332%。这个差别是比较大的,这表明使用 Firefox 时选择不同的 JavaScript 库会对性能有影响。
但是要记住,这些结论仅基于一个浏览器的结果。这是基于 Firefox 3.0.3 得出的结论。现在我们进入下一小节,我将在不同的浏览器上运行该测试。
在不同浏览器上的JavaScript 性能
面对不同浏览器运行 JavaScript 会得出的不同结果(性能和时间都不同),许多初级 Web 程序员觉得不可思议。尽管这对初级 Web 程序员而言是个挫折(他们担心要编写额外的代码来处理不同的浏览器),但是有经验的 Web 程序员在 Netscape 和 Internet Explorer 的早期就知道如何处理该问题。这也是使用 JavaScript 库的一个亮点,因为它们都谨慎处理许多或大部分浏览器差异。
JavaScript 速度差异的主要原因是每个浏览器都使用自己的 JavaScript 引擎。JavaScript 引擎是用于解析 JavaScript 并根据 Web 应用程序执行它的本机代码。因此,JavaScript 的执行速度与底层引擎直接相关。在最近几个月,许多浏览器公司越来越关注他们的浏览器的性能,这是有原因的。随着某些页面的 JavaScript 变得日益复杂,JavaScript 引擎的快慢能够影响 Web 应用程序的响应速度。因此,当 Google 和 Firefox 等公司谈论它们的 JavaScript 引擎时,它们就会谈及下一代引擎的速度要快 10 倍。这对 Web 应用程序而言是很重要的,因为底层 JavaScript 引擎的速度直接导致更复杂的 Web 应用程序的出现。
现在,您知道 JavaScript 引擎是 JavaScript 执行速度的一个因素,那么让我们在不同的浏览器上运行刚才在 Firefox 上运行的测试,并尝试找出不同的引擎对 JavaScript 性能的影响。记住,这个测试与我前面在 Firefox 上运行的测试是一样的,因此除了 JavaScript 引擎以外,其他所有东西都是相同的。图 2 显示了测试结果。
图 2. 性能测试 2 的结果
看完这些测试结果之后,您首先注意到的是在这些浏览器中运行得到的时间差很大。在 Chrome 1.0 上运行 jQuery 需要 168 毫秒,而在 IE6 上运行需要 1728 秒。这是难以置信的时间差!jQuery 选择方法在 IE6 上运行比在 Chrome 上运行慢 10 倍!现在,您知道为什么 Google 喜欢夸耀它的 JavaScript 引擎,以及为什么某些浏览器很少介绍自己的 JavaScript 引擎。这些差别还是比较大的。
您应该注意到,jQuery 在 Firefox 或一些其他浏览器上运行时速度排在第 3 位,而在另一些浏览器上排在第 1 位。事实上,这些结果表明,根据性能进行分类的话,这些库可以分为两组,而不管使用什么浏览器。Mootools、Dojo 和 jQuery 通常属于一个组别,而 Prototype 和 YUI 属于另一个组别,前一组要比后一组快得多。
性能测试结论
我觉得所有这些结论都需要专门花一个小节进行阐述,因为它们对 JavaScript 开发人员非常重要。我仍然尝试总结上面的性能结果,并且没有忘记本文是以 jQuery 为主题的。
◆ 结论 1:Mootools、jQuery 和 Dojo 在性能方面不分上下。
查看它们在所有 5 个浏览器上进行的测试,在求取平均值之后,您可以看到这 3 个库的性能几乎是一样的。(理想情况下,我们应该调查每个浏览器的市场份额。但是调整这些数字很难,因为使用 JavaScript 库的站点不一定由 “平均用户” 访问)。
图 3. 测试结果的平均值(Mootools、jQuery 和 Dojo)
◆ 结论 2:Prototype 和 YUI 的性能很慢。
看看这两个库在 5 个浏览器中的测试结果与 jQuery 的对比。在求取它们的平均值之后,您可以发现这两个库的性能差别有多大。它们在任意浏览器中平均比 jQuery 慢 300%。
图 4. 测试结果的平均值(jQuery、Prototype 和 YUI)
◆ 结论 3:如果对性能要求比较高时,选择 Mootools、jQuery 和 Dojo 之一获得的性能几乎一样。
根据以上的平均值,选择这 3 个库之一比选择另外两个库之一能够获得更多的性能优势。从在所有浏览器上运行得出的平均值看,它们的性能是相当的。因此,当您选择 JavaScript 库时,选择这 3 个库之一是不会错的。
◆ 结论 4:如果对性能要求比较高时,不要选择 Prototype 或 YUI。
如果要求 JavaScript 库具有较高的性能,或者打算创建一个大型的 JavaScript 项目,那么就不应该选择这两个库之一。这两个库的性能要比其他库逊色得多。
◆ 结论 5:浏览器对性能的影响是 JavaScript 库的 9 倍。
我认为这是本文所有结论中最重要的结论。您可以在特定情况下讨论哪个 JavaScript 库最快,但它最终的影响却是很小的!对于性能而言,浏览器的影响比库本身要大得多。回顾一下图 3 和图 4 的平均值,您可以看到 3 个最快的库中,最慢那个(Dojo)仅比最快那个(jQuery)慢 15%。只有 15%!然而,您看看 jQuery 在最快的浏览器(Chrome 1.0)和最慢的浏览器(IE6)上运行的速度差别,这个差别竟然达到 1000%!从这两个数字看,15% 对 1000% 而言是微不足道的。至此,关于 3 个较快的库中哪个是最快的争论可以停止了,因为它们对最终结果的影响是微乎其微的。
◆ 结论 6:如果 JavaScript 性能对 Web 应用程序很重要,并且您可以控制选择什么浏览器,那么就选择最快的浏览器!
在某些情况下,您可以控制使用什么浏览器访问站点。如果能够控制使用什么浏览器,那么您就是很幸运的。我就碰到这样幸运的项目。在这种情况下,如果您拥有一个复杂的 JavaScript 应用程序,或者您认为性能很重要,那么您就应该控制用户用于访问 Web 应用程序的浏览器。这些测试已经清楚地显示了浏览器的影响。如果您的 JavaScript 应用程序的访问量很大,那么您可以告诉用户,他们必须 使用 Chrome。
◆ 结论 7:如果您不能控制用户使用的浏览器,那么要首先考虑在 IE6 上的性能。
但是,在大部分情况下,我们都无法控制用户使用什么浏览器访问我们的站点。不过,很大一部分用户都使用 IE 6 浏览网页。到目前为止的测试中,这个浏览器的 JavaScript 引擎是最慢的。但是由于仍然有大量用户使用它,并且良好的 Web 设计需要 “适应最糟糕的情况”,这意味着您可以考虑根据 IE6 设计您的 JavaScript 应用程序。
jQuery 性能调优
本文的第二部分将讨论如何改进 jQuery 代码的性能。前一部分表明选择 jQuery 作为 JavaScript 库指向了正确的性能方向。如果您正在阅读本文,您可能已经使用了 jQuery。但是底层库速度快并不意味着您编写的所有代码都是高质量的。如果您没有回过头来想想应该怎么做,使用 jQuery 仍然会编写出非常慢的代码。
这个部分介绍一些性能调优知识,以及改进 jQuery 代码速度的最佳实践技巧。
技巧 #1 - 尽可能多地通过 ID 进行搜索,而不是 CLASS
在 jQuery 代码中两种常见的搜索技术是通过元素的 ID 进行搜索和通过元素的 CLASS 进行搜索。在使用常规 JavaScript 的 JavaScript 库之前,通过 ID 查找页面元素还是相当简单的。可以使用 getElementById() 方法快速找到元素。但是如果没有 JavaScript 库,要查找 CLASS 会更加困难,在必要情况下,我们还通过在其 ID 中进行编码帮助查找。使用 jQuery 时,搜索 CLASS 就像搜索页面上的 ID 一样简单,因此这两个搜索似乎是可互换的。然而实际情况并非如此。通过 ID 搜索比通过 CLASS 搜索要快得多。当通过 ID 进行搜索时,jQuery 实际上仅使用内置的 getElementById() 方法,但通过 CLASS 进行搜索时必须遍历页面上的所有元素,以查找匹配项。很明显,当页面越大并且越复杂时,通过 CLASS 进行搜索会导致响应非常慢,而通过 ID 进行搜索不会随着页面变大而变慢。
前面运行的 jQuery 性能测试结果支持这一数据。让我们查看每个测试,看看需要注意 jQuery 代码的什么地方。在这个例子中,分别看看通过 ID 和 CLASS 进行搜索时的测试结果(图 5)。
图 5. ID 搜索和 CLASS 搜索对比
这些测试是不同的,但它们得出的数据表明通过 ID 进行搜索比通过 CLASS 进行搜索快得多。这如何影响到 jQuery 代码?在编写搜索时,您要记住这些技巧:如果既可选择 CLASS 又可选择 ID,那么通常要选择 ID。如果需要在您的代码中搜索某些元素,一定要给它们分配 ID。
清单 1 显示了一个实际的 jQuery 测试,您可以在您的机器上运行它对此进行验证:
清单 1. CLASS 和 ID
- $(document).ready(function() {
- console.info("Start Test");
- var d = new Date();
- console.info(d.getSeconds() + " " + d.getMilliseconds());
- var testBody = "";
- for (var i=0; i<1000; i++)
- {
- testBody += "<div class='testable"+i+"'>";
- }
- $("body").append(testBody);
- for (var i=0; i<1000; i++)
- {
- $(".testable"+i);
- }
- var d = new Date();
- console.info(d.getSeconds() + " " + d.getMilliseconds());
- console.time("Start ID Test");
- testBody = "";
- for (var i=0; i<1000; i++)
- {
- testBody += "<div id='testable"+i+"'>";
- }
- $("body").append(testBody);
- for (var i=0; i<1000; i++)
- {
- $("#testable"+i);
- }
- var d = new Date();
- console.info(d.getSeconds() + " " + d.getMilliseconds());
- console.info("End Test");
- });
ID 测试耗时 218 毫秒,而 CLASS 测试耗时 19.9 秒!甚至在专门为该测试构建的简单页面上,ID 搜索也要比 CLASS 搜索快 100 倍!
技巧 #2 - 提供尽可能多的搜索信息
jQuery 提供如此多的页面元素搜索方法,有时您难以指出哪种方法是最好的。有一条经验是不会错的,即为搜索参数提供尽可能多的信息。因此,假如您正在搜索带有 “clickable” CLASS 的所有页面元素,如果您提前知道仅有 DIV 附带有 CLASS,那么就能提高搜索性能。所以,搜索 “div.clickable” 会更加快。图 6 显示了支持该技巧的结果。
图 6. 尽可能多地提供信息
考虑到底层 JavaScript 代码之后,这就不足为奇了。通过提供元素标记,与 CLASS 参数匹配的搜索元素数量将大大减少,从而将搜索性能提升至与本例中的 ID 搜索相当。
开发人员在编写 jQuery 选择方法时不能偷懒,尽管 jQuery 的简单让人产生偷懒的欲望。简单让您放松了警惕。搜索机制变得如此简单,让我们倾向于仅输入一条信息。然而,您应该总是尽可能多地输入信息,尤其是已知信息。清单 2 显示了一个很好的例子。
清单 2. 提供充足的信息
- // Assume there are 50 of these in some giant form, and you need to validate
- // these fields before they are submitted, and there are hundreds of other
- // elements on the page as well
- <input type=text class="notBlank">
- // the "bad" way to validate these fields
- $(".notBlank").each(function(){
- if ($(this).val()=="")
- $(this).addClass("error");
- });
- // the "good" way to validate these fields
- $("input.notBlank").each(function(){
- if ($(this).val()=="")
- $(this).addClass("error");
- });
- // the "best" way to validate these fields
- $("input:text.notBlank").each(function(){
- if ($(this).val()=="")
- $(this).addClass("error");
- });
技巧 #3 - 缓存选择器
最后一个性能技巧利用了几乎所有 jQuery 选择器都返回 jQuery 对象这个特性。这意味着在理想的情况下,您仅需要运行选择器一次,并且能够轻松地将所有函数连接在一起,或缓存结果供以后使用。您也不要担心缓存,因为与总体可用内存相比,返回的对象是很小的。
清单 3 给出了一些关于如何利用缓存的例子。
清单 3. 缓存
在我的最后一个关于性能
- // Imagine a function that hides all the div's with a class of "hideable"
- // when a button is pressed. These DIV's might reappear later when
- // working with the page, so the button can be pressed any number of
- // times, and the DIV's that have reappeared
- // will again be made to be hidden.
- $("#ourHideButton").click(function(){
- $(".hideable").hide();
- });
- // As you saw in the CLASS versus ID example, though, a search for
- // CLASS is very inefficient
- // If this button is pressed often, it could lead to a slow response
- // Instead of the above example, you should write your code like this
- var hideable;
- $("#ourHideButton").click(function(){
- if (hideable)
- hideable.hide();
- else
- hideable = $(".hideable").hide();
- });
- // You can cache your search in a JavaScript variable and reuse it every time
- // the button is pressed. Because jQuery almost always returns the
- // jQuery object, you can save it the first time it is called for future use
的示例代码中,将查看我在本系列的第一篇文章中提到的小部件(见 参考资料)。这个小部件是在表的左上角上的复选框,它允许您选择或取消选择该列上的所有复选框。这个小部件在电子邮件应用程序中非常常见,用于选择或取消选择所有消息。
清单 4. 性能改进
- // Here is the code as I originally presented it in that article. Let's see
- // if we can improve the performance in any way from the things we learned here
- function selectAll()
- {
- var checked = $("#selectall").attr("checked");
- $(".selectable").each(function(){
- var subChecked = $(this).attr("checked");
- if (subChecked != checked)
- $(this).click();
- });
- }
- // Here's the improved function. The search for the ID of "selectall" is
- // OK as-is, because we saw how fast the ID search is.
- // The search for the CLASS of "selectable" was not well-designed though,
- // because we saw a search by CLASS is very inefficient.
- // First step was improving the search by supplying as much information as we know.
- // We narrowed the search to only checkboxes with the CLASS of selectable.
- // This should improve our search
- // Further, we can cache this search because we will only need to perform it once
- // Finally, we can perform this search before the selectall checkbox is even
- // checked (when the page is finished loading), so that the search is completed
- // and cached before the user even uses it.
- // These 3 simple performance steps gave me a 200% increase in speed when tested
- // on a page with 200 rows of data.
- var selectable = $(":checkbox.selectable");
- function selectAll()
- {
- var checked = $("#selectall").attr("checked");
- selectable.each(function(){
- var subChecked = $(this).attr("checked");
- if (subChecked != checked)
- $(this).click();
- });
- }
关于性能的要点
使用 JavaScript 时,速度和性能绝对不是小问题。在现实中,创建 jQuery 的开发人员和处理浏览器内置 JavaScript 引擎的开发人员都非常关注性能问题。事实上,在最近 6 个月以来,浏览器开发的最重要问题就是 JavaScript 引擎的速度。浏览器开发者都致力于在下一年迅速提升 JavaScript 的执行性能,从而大大提高 jQuery 代码和 JavaScript 引擎的速度。来自这些 “速度之战” 的消息表明,提升 JavaScript 性能是大势所趋。领导 jQuery 项目的 John Resig 一直都在谈论他的最新 “Sizzle” 选择引擎。他从头编写了一个选择引擎,并声称初步结果表明它比 Firefox 要快 4 倍。这是巨大的速度提升!在我撰写本文的最后部分时,jQuery 1.3 已经发布,并且包含了 Sizzle 选择引擎。jQuery 声称,在所有浏览器上运行的总体结果表明选择引擎的 1.3 版本比 1.2.6 版本的快 49%。此外,1.3 发布版在 HTML 注入(向 DOM 添加新的元素)上改进了 6 倍,在函数定位上改进了 3 倍。在我完成本文时,很多人都更新到了最新的 jQuery 发布版,这是非常令人激动的!
影响 JavaScript 性能的另一个因素是浏览器,如前所述,它的影响是所选的库的 9 倍。Firefox 和 Chrome 在 “最快 JavaScript 引擎” 之战中各有胜负,而 Safari 的参与让竞争更加激烈。从我们上面的测试中,可以看到 Chrome 在 JavaScript 引擎性能方面远远超过 Firefox,但是 Firefox 3.1 将包含新的 Tracemonkey JavaScript 引擎,届时其速度将比当前的 JavaScript 引擎 3.0 快 20 至 40 倍(这是他们声称的,不是我的观点),真不可思议!
在未来一两年内,您将看到底层 JavaScript 引擎和 JavaScript 库的速度得到巨大改进,从而导致使用 JavaScript 的 Web 应用程序将变得更加复杂。
如果您正在决定是使用 JavaScript 库还是自己编写 JavaScript,那么需要考虑的另一件事情是处理和调试 JavaScript 库所需的全部工作。最近几年以来,有数百位用户一直在维护库中的每一个函数。您可能要忙到深夜,甚至筋疲力尽地编写自己的函数。您更相信谁呢?另外,即使您能编写出比 jQuery 库更快的代码,您是否想过使用 jQuery 库能够获得更多的优势?您是否为了辛苦地编写自己的代码而放弃使用非常便利的 jQuery 及其函数库?自己编写代码不仅需要大量时间,并且还会产生更多 bug,因此我还是建议使用现成的 jQuery 库。
我最后讨论的要点可能会让一些人沮丧,但是我们必须考虑编写这些 jQuery 库的程序员。他们当中的一些是最棒的,并且他们编写的超级优秀的代码(一般人不能编写这样出色的代码)都收入到这些库中。我承认自己远远不如编写 jQuery 库的程序员。因此,为何不利用他们的智慧?
结束语
本文从总体上讨论了 jQuery 和 JavaScript 库的性能。通过对选择方法进行大量的测试,您看到这些库之间的速度存在巨大的差距,并且 jQuery 是最快的库之一。不过,即使您选择了最快的 JavaScript 库,还是不能解决 Web 应用程序的性能问题,因为浏览器对性能的影响比库强 9 倍。如果您能够控制用户使用特定的 Web 浏览器,那么就让他们使用最快的浏览器。找到能够最快地运行您的 Web 应用程序的浏览器,并让用户通过使用它从中受益。理想情况下,让最慢的 JavaScript 浏览器消失意味着出现更快的 Web 应用程序。
我还提供了关于 jQuery 性能的 3 个技巧。尽管有好几个站点都提供了关于 jQuery 性能的技巧,但是这 3 个技巧是最有效的。3 个技巧肯定比 50 个技巧容易记住,但其他技巧也是很好的,我将在 参考资料 部分指出另外一些技巧。不过,如果您在编写代码时记住了这 3 个技巧,将会获得巨大的性能提升。应该永远记住的 3 个 jQuery 技巧是:通过 ID 搜索比通过 CLASS 搜索快 100 倍,尽可能多地提供搜索信息,以及尽量缓存选择。
最后,我们快速查看了 jQuery 和浏览器的 JavaScript 引擎即将推出的改进。在我撰写本文的结尾部分时,jQuery 1.3 已经发布了,它承诺在选择和代码的其他方面实现跳跃式性能改进。此外,Firefox 还承诺它的下一代 JavaScript 引擎会快 20 至 40 倍!这些迹象表明,在未来的一两年内,JavaScript 环境会在性能上取得重大突破。在不久的将来,复杂的 Web 应用程序会日益流行,因为快速运行这些程序的条件已经成熟。
在本文结束时,我们应该回过头来重新检验这句话 “jQuery 在简单的页面上无可挑剔,但在复杂的页面上性能极其低下。在解决性能问题之前,您必须对复杂页面使用常规的 JavaScript。”根据我的经验,jQuery 的 “性能问题” 一样出现在 “常规的 JavaScript” 中。事实上,真正影响性能的不是 jQuery 或 JavaScript,而是浏览器。因此我同意这样的观点:使用设计不良的 jQuery 代码的复杂页面运行在 IE6 上时会导致糟糕的性能。不过,我希望您已经了解我要阐述的思想:只要拥有良好的 jQuery 代码、运用 3 个最重要的技巧并选择最快的浏览器,那么即使运行最复杂的页面也不会出现明显的性能问题。
原文链接:http://www.ibm.com/developerworks/cn/web/wa-aj-advjquery/