使用jQuery动态生成HTML的正确方式

21
我发现这个话题上有一些不同且相互矛盾的答案。
我正在开发一个应用程序,它主要通过jQuery动态生成HTML,并基于从底层API获取的JSON数据进行操作。
我的一些同事(私下里)告诉我,最好的方法是像这样做:
var ul = $("<ul>").addClass("some-ul");
$.each(results, function(index) {
  ul.append($("<li>").html(this).attr("id", index));
});
$("body").append($("<div>").attr("id", "div-id").addClass("some-div").append(ul));

等等。

我被告知的原因是“直接更新DOM而不是解析HTML来实现它”。

然而,我看到很多类似这样的代码(同一个例子):

var toAppend = '<div class="some-div" id="div-id"><ul>';
$.each(results, function(index) {
  toAppend += '<li id="' + index + '">' + this + '</li>';
});
toAppend += '</ul></div>'

我个人认为这种方法不够优雅,但它是否更好呢?我谷歌了几分钟,找到了这篇文章。基本上,它是关于通过使用字符串连接 - 我的“第二种方法”来显著提高性能的。
这篇文章的主要问题在于它发布于2009年,讨论的jQuery版本是1.3。今天,当前版本是1.6.4,可能会有很大的不同。这是我已经发现的大多数有关此主题的文章的问题,我对它们的可信度也有些怀疑。
这就是为什么我决定在这里发布问题并询问 - 基于性能,哪种生成DOM的方法才是正确的?
重要编辑:
我编写了一个小基准测试来测试哪种方法考虑性能更好。 jsFiddle - 连接版 jsFiddle - 数组连接版
代码:
var text = "lorem ipsum";
var strings = $("#strings");
var objects = $("#objects");
var results = $("#results");

// string concatenation
var start = new Date().getTime();
var toAppend = ['<div class="div-class" id="div-id1"><ul class="ul-class" id="ul-id1">'];
for (var i = 1; i <= 20000; i++) {
    toAppend[i] = '<li class="li-class" id="li-id1-' + i + '">' + text + '</li>';
}
toAppend[i++] = '</ul></div>';
results.append(toAppend.join(""));
strings.html(new Date().getTime() - start);

// jquery objects
var start = new Date().getTime();
var ul = $("<ul>").attr("id", "ul-id2").addClass("ul-class");
for (var i = 0; i < 20000; i++) {
    ul.append($("<li>").attr("id", "li-id2-" + i).addClass("li-class"));
}
results.append($("<div>").attr("id", "div-id2").addClass("div-class").append(ul));
objects.html(new Date().getTime() - start);

似乎在Firefox 7中,操作字符串比使用jQuery对象和方法快(大约快7倍)。但是我可能是错误的,特别是如果这个“基准测试”的代码中有任何错误或性能降低的错误。请随意进行任何更改。
注意:我使用了Array join,因为之前提到的文章,而不是实际的连接。
编辑:根据@hradac的建议,在基准测试中使用了实际的字符串连接,事实上确实改善了时间。

http://www.artzstudio.com/2009/04/jquery-performance-rules/ - NimChimpsky
3
如果你经常做这样的事情,你可能会考虑使用jQuery模板。它专门为解决这个问题而建立。http://api.jquery.com/category/plugins/templates/ - Malevolence
@NimChimpsky 我知道这篇文章,它是我每天开发jQuery时的指南之一。它涵盖了我的一部分问题,但没有说明哪种约定更好。 - Przemek
1
我建议实际使用字符串连接而不是数组连接。过去数组连接速度更快,但事情已经改变,现在不再是这样了。http://jsperf.com/array-join-vs-string-connect - hradac
1
你忘记在你的jQuery示例中设置文本了。添加.text(text)会使它显著变慢,但确实展示了jQuery可以关闭XSS向量的优势。 - Brian Nickel
5个回答

12
首先,这种微基准测试几乎从不告诉您真正想知道的内容。其次,您的基准测试是多样的而且并不相等。例如,您的第一个示例生成类似于以下内容的行:
<li class="li-class" id="li-id1-493">lorem ipsum</li>

你的第二行应该像这样:

<li id="li-id2-0" class="li-class"></li>

请注意不同的元素顺序和缺少“lorem ipsum”文本。也没有尝试在测试之间清除结果div,以避免由于前20K个结果已经存在而导致的性能问题。 但是超出这些问题的是一个问题:“这真的会影响客户端用户体验吗?”认真的?你这样呈现如此大量的文本,以至于你可以看到替代方法呈现文本之间的明显差异? 我会回想其他人所说的,使用模板引擎。最快的引擎确实非常快,甚至有预编译选项,让您重新渲染相同的模板并获得快速结果。所以不要相信我。相反,请相信演示。这是我的jsFiddle,用于演示新的JsRender库的性能,该库应该替换jQuery模板引擎...

http://jsfiddle.net/LZLWM/10/

注意:JsRender加载到Fiddle中可能需要几秒钟的时间。这是因为我直接从GitHub获取它,而GitHub并不特别擅长这样做。我不建议在实际应用中这样做。但是它不会改变时间,并且在jsFiddle开始将模板引擎作为选项合并之前是必要的。
请注意,第二个例子更接近真实世界的例子,使用JSON作为起点生成20,000行,时间与您最快的测试大致相同(在我的机器上差异小于50毫秒)。还要注意,代码和模板都比任何一堆追加和字符串连接混乱的代码容易理解和使用得多。我需要多少次迭代才能让我的模板正确,而你正在做什么呢?
在这个微观优化水平上停止使用复杂的东西并浪费时间,这可能甚至不是必要的。相反,使用像这样的模板(或其他几个好的模板引擎之一),确保已经打开了过期标头,使用CDN,服务器上已经打开了gzip压缩等。所有YSlow告诉你要做的事情,因为这将完全淹没你在这里看到的效果。

谢谢您,这恰好是我正在寻找的答案。只是为了澄清: - Przemek
我意识到我的所谓基准测试中存在明显的漏洞,我写得不是很仔细,也有评论指出了这一点。只是我没有时间去纠正它。 - Przemek
我只是想跟进一下我的JsRender使用情况。我选择它是因为我在另一个上下文中正在尝试它。请不要在此时(2011年10月)使用它,因为它真的还没有准备好投入使用。任何其他几个模板库都是更好的选择,并且在它们之间切换并不难,因为它们中的许多都具有类似的结构,只是稍微不同的语法来调用它。 - John Munsch
你说“使用模板引擎”就把我迷住了。仅供更新,自2011年以来,[AngularJS](http://angularjs.org/)和[Knockout](http://knockoutjs.com/)是目前最受认可的两个。 - ruffin
回到这个问题,因为有人投票,我想说我很长时间以来一直使用Handlebars作为JavaScript的模板引擎,并且非常满意。如果你只需要一个模板引擎,我会选择它。如果你想要更多不仅仅是模板引擎,我现在已经使用AngularJS接近一年了,也非常满意。 - John Munsch

6

2
注意:此插件已作为BETA版本发布,现已停止活跃开发和维护。文档仍然保留在这里(供参考),直到有一个合适的替代模板插件准备好为止。 - D'Arcy Rittich
这是一个旧答案,不再是理想的解决方案。jQuery已经弃用了模板支持(如果还没有完全删除的话)。模板引擎随处可见;目前我正在使用DOM片段进行客户端模板化,但理想的解决方案完全取决于要解决的问题。 - John Strickler
此链接已失效! - LuckyLikey

4
通常情况下,易读性比性能更为重要,除非您实际上存在问题。我会选择第二种方法,因为它很容易被识别为一个标准的HTML片段,并且我认为它更不容易出现错误。
使用第一种方法,我必须解析大量的代码才能想象出最终的字符串将是什么样子(因此需要进行哪些修改)。

目前我没有问题,但是我正在开发的应用程序将不断处理更多的数据,这可能会成为一个问题。 - Przemek
@Przemek 或许使用第二种方法的另一个原因是,虽然我无法确认它是否提供更好的性能。不过你可以自己轻松测试一下。 - D'Arcy Rittich

2
你的第一种方法更好。思路是在必要时才操作DOM。在这两个示例中,你都是在内存中创建UL,然后最后使用body.append()将其附加到DOM上。
然而,在你的第一个示例中构建树是首选方式。字符串拼接速度略慢(当然,我们谈论的是毫秒级别)。但是,如果你需要在页面上执行此操作多次,那么这可能会变得重要。
我会稍微优化你的代码,但只是为了提高可读性。
var div = $("<div>").attr("id", "div-id").addClass("some-div");
var ul = $("<ul>").addClass("some-ul");
$.each(results, function(index) {
    var li = $("<li>").html(this).attr("id", index);
    ul.append(li);
});
div.append(ul);

// you haven't touched the DOM yet, everything thus far has been in memory

$("body").append(div); // this is the only time you touch the DOM

2
基本上,第一种方法使用多个.innerHTML方法调用来创建结果,而第二种方法只使用一个。这是导致执行所需时间差异的主要原因。
如果字符串变得非常大,我建议使用第三种方法,即使用数组。
var toAppend = ['<div class="some-div" id="div-id"><ul>'];
$.each(results, function(index) {
  toAppend.push('<li id="' + index + '">' + this + '</li>');
});
toAppend.push('</ul></div>');
$(target).append(toAppend.join(""));

我通常只使用数组方法以保持一致性。

编辑:hradac是正确的,现在连接方法更快了。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接