[翻译]High Performance JavaScript(009)

第三章  DOM Scripting  DOM编程

 

    DOM scripting is expensive, and it's a common performance bottleneck in rich web applications. This chapter discusses the areas of DOM scripting that can have a negative effect on an application's responsiveness and gives recommendations on how to improve response time. The three categories of problems discussed in the chapter include:

    对DOM操作代价昂贵,在富网页应用中通常是一个性能瓶颈。本章讨论可能对程序响应造成负面影响的DOM编程,并给出提高响应速度的建议。本章讨论三类问题:

 

• Accessing and modifying DOM elements

访问和修改DOM元素

 

• Modifying the styles of DOM elements and causing repaints and reflows

修改DOM元素的样式,造成重绘和重新排版

 

• Handling user interaction through DOM events

通过DOM事件处理用户响应

 

    But first—what is DOM and why is it slow?

    但首先——什么是DOM?他为什么慢?

 

DOM in the Browser World  浏览器世界中的DOM

 

    The Document Object Model (DOM) is a language-independent application interface (API) for working with XML and HTML documents. In the browser, you mostly work with HTML documents, although it's not uncommon for web applications to retrieve XML documents and use the DOM APIs to access data from those documents.

    文档对象模型(DOM)是一个独立于语言的,使用XML和HTML文档操作的应用程序接口(API)。在浏览器中,主要与HTML文档打交道,在网页应用中检索XML文档也很常见。DOM APIs主要用于访问这些文档中的数据。

 

    Even though the DOM is a language-independent API, in the browser the interface is implemented in JavaScript. Since most of the work in client-side scripting has to do with the underlying document, DOM is an important part of everyday JavaScript coding.

    尽管DOM是与语言无关的API,在浏览器中的接口却是以JavaScript实现的。客户端大多数脚本程序与文档打交道,DOM就成为JavaScript代码日常行为中重要的组成部分。

 

    It's common across browsers to keep DOM and JavaScript implementations independent of each other. In Internet Explorer, for example, the JavaScript implementation is called JScript and lives in a library file called jscript.dll, while the DOM implementation lives in another library, mshtml.dll (internally called Trident). This separation allows other technologies and languages, such as VBScript, to benefit from the DOM and the rendering functionality Trident has to offer. Safari uses WebKit's WebCore for DOM and rendering and has a separate JavaScriptCore engine (dubbed SquirrelFish in its latest version). Google Chrome also uses WebCore libraries from WebKit for rendering pages but implements its own JavaScript engine called V8. In Firefox, Spider-Monkey (the latest version is called TraceMonkey) is the JavaScript implementation, a separate part of the Gecko rendering engine.

    浏览器通常要求DOM实现和JavaScript实现保持相互独立。例如,在Internet Explorer中,被称为JScript的JavaScript实现位于库文件jscript.dll中,而DOM实现位于另一个库mshtml.dll(内部代号Trident)。这种分离技术允许其他技术和语言,如VBScript,受益于Trident所提供的DOM功能和渲染功能。Safari使用WebKit的WebCore处理DOM和渲染,具有一个分离的JavaScriptCore引擎(最新版本中的绰号是SquirrelFish)。Google Chrome也使用WebKit的WebCore库渲染页面,但实现了自己的JavaScript引擎V8。在Firefox中,JavaScript实现采用Spider-Monkey(最新版中称作TraceMonkey),与其Gecko渲染引擎相分离。

 

Inherently Slow  天生就慢

 

    What does that mean for performance? Simply having two separate pieces of functionality interfacing with each other will always come at a cost. An excellent analogy is to think of DOM as a piece of land and JavaScript (meaning ECMAScript) as another piece of land, both connected with a toll bridge (see John Hrvatin, Microsoft, MIX09, http://videos.visitmix.com/MIX09/T53F). Every time your ECMAScript needs access to the DOM, you have to cross this bridge and pay the performance toll fee. The more you work with the DOM, the more you pay. So the general recommendation is to cross that bridge as few times as possible and strive to stay in ECMAScript land. The rest of the chapter focuses on what this means exactly and where to look in order to make user interactions faster.

    这对性能意味着什么呢?简单说来,两个独立的部分以功能接口连接就会带来性能损耗。一个很形象的比喻是把DOM看成一个岛屿,把JavaScript(ECMAScript)看成另一个岛屿,两者之间以一座收费桥连接(参见John Hrvatin,微软,MIX09,http://videos.visitmix.com/MIX09/T53F)。每次ECMAScript需要访问DOM时,你需要过桥,交一次“过桥费”。你操作DOM次数越多,费用就越高。一般的建议是尽量减少过桥次数,努力停留在ECMAScript岛上。本章将对此问题给出详细解答,告诉你应该关注什么地方,以提高用户交互速度。

 

DOM Access and Modification  DOM访问和修改

 

    Simply accessing a DOM element comes at a price—the "toll fee" discussed earlier. Modifying elements is even more expensive because it often causes the browser to recalculate changes in the page geometry.

    简单来说,正如前面所讨论的那样,访问一个DOM元素的代价就是交一次“过桥费”。修改元素的费用可能更贵,因为它经常导致浏览器重新计算页面的几何变化。

 

    Naturally, the worst case of accessing or modifying elements is when you do it in loops, and especially in loops over HTML collections.

    当然,访问或修改元素最坏的情况是使用循环执行此操作,特别是在HTML集合中使用循环。

 

    Just to give you an idea of the scale of the problems with DOM scripting, consider this simple example:

    为了给你一个关于DOM操作问题的量化印象,考虑下面的例子:

 

function innerHTMLLoop() {
  for (var count = 0; count < 15000; count++) {
    document.getElementById('here').innerHTML += 'a';
  }
}

    This is a function that updates the contents of a page element in a loop. The problem with this code is that for every loop iteration, the element is accessed twice: once to read the value of the innerHTML property and once to write it.

    此函数在循环中更新页面内容。这段代码的问题是,在每次循环单元中都对DOM元素访问两次:一次读取innerHTML属性能容,另一次写入它。

 

    A more efficient version of this function would use a local variable to store the updated contents and then write the value only once at the end of the loop:

    一个更有效率的版本将使用局部变量存储更新后的内容,在循环结束时一次性写入:

 

function innerHTMLLoop2() {
  var content = '';
  for (var count = 0; count < 15000; count++) {
    content += 'a';
  }
  document.getElementById('here').innerHTML += content;
}

    This new version of the function will run much faster across all browsers. Figure 3-1 shows the results of measuring the time improvement in different browsers. The y-axis in the figure (as with all the figures in this chapter) shows execution time improvement, i.e., how much faster it is to use one approach versus another. In this case, for example, using innerHTMLLoop2() is 155 times faster than innerHTMLLoop() in IE6.

    在所有浏览器中,新版本运行速度都要快得多。图3-1显示了在不同浏览器上测量到的速度提升。Y轴的数字显示出速度提升,比方说,一个比另一个快了多少倍。例如在IE6中,innerHTMLLoop2()比innerHTMLLoop()快了155倍。

Figure 3-1. One benefit of staying within ECMAScript: innerHTMLLoop2() is hundreds of times faster
than innerHTMLLoop()

图3-1  innerHTMLLoop2()比innerHTMLLoop()快上百倍

 

    As these results clearly show, the more you access the DOM, the slower your code executes. Therefore, the general rule of thumb is this: touch the DOM lightly, and stay within ECMAScript as much as possible.

    这些结果清楚地表明,你访问DOM越多,代码的执行速度就越慢。因此,一般经验法则是:轻轻地触摸DOM,并尽量保持在ECMAScript范围内。

 

innerHTML Versus DOM methods  innerHTML与DOM方法比较

 

    Over the years, there have been many discussions in the web development community over this question: is it better to use the nonstandard but well-supported innerHTML property to update a section of a page, or is it best to use only the pure DOM methods, such as document.createElement()? Leaving the web standards discussion aside, does it matter for performance? The answer is: it matters increasingly less, but still, innerHTML is faster in all browsers except the latest WebKit-based ones (Chrome and Safari).

    多年来,在web开发者社区已经对此问题进行了许多讨论:更新页面时,使用虽不标准却被良好支持的innerHTML属性更好呢,还是使用纯DOM方法,如document.createElement()更好呢?如果不考虑标准问题,它们的性能如何?答案是:性能差别不大,但是,在所有浏览器中,innerHTML速度更快一些,除了最新的基于WebKit的浏览器(Chrome和Safari)。

 

    Let's examine a sample task of creating a table of 1000 rows in two ways:

    让我们检验一个例子,用两种方法来创建一个1000行的表:

 

• By concatenating an HTML string and updating the DOM with innerHTML

通过构造一个HTML字符串,然后更新DOM的innerHTML属性

 

• By using only standard DOM methods such as document.createElement() and document.createTextNode()

通过标准DOM方法document.createElement()和document.createTextNode()

 

    Our example table has content similar to content that would have come from a Content Management System (CMS). The end result is shown in Figure 3-2.

    我们例子中的表内容从一个内容管理系统(CMS)中获得,其显示结果如图3-2。

Figure 3-2. End result of generating an HTML table with 1,000 rows and 5 columns

图3-2  创建一个1000行5列的HTML表

 

    The code to generate the table with innerHTML is as follows:

    使用innerHTML创建表的代码如下:

 

function tableInnerHTML() {
  var i, h = ['<table border="1" width="100%">'];
  h.push('<thead>');
  h.push('<tr><th>id<//th><th>yes?<//th><th>name<//th><th>url<//th><th>action<//th><//tr>');
  h.push('<//thead>');
  h.push('<tbody>');
  for (i = 1; i <= 1000; i++) {
    h.push('<tr><td>');
    h.push(i);
    h.push('<//td><td>');
    h.push('And the answer is... ' + (i % 2 ? 'yes' : 'no'));
    h.push('<//td><td>');
    h.push('my name is #' + i);
    h.push('<//td><td>');
    h.push('<a href="http://example.org/' + i + '.html">http://example.org/' + i + '.html<//a>');
    h.push('<//td><td>');
    h.push('<ul>');
    h.push(' <li><a href="edit.php?id=' + i + '">edit<//a><//li>');
    h.push(' <li><a href="delete.php?id="' + i + '-id001">delete<//a><//li>');
    h.push('<//ul>');
    h.push('<//td>');
    h.push('<//tr>');
  }
  h.push('<//tbody>');
  h.push('<//table>');
  document.getElementById('here').innerHTML = h.join('');
};

 

    In order to generate the same table with DOM methods alone, the code is a little more verbose:

    如果使用DOM方法创建同样的表,代码有些冗长。

 

function tableDOM() {
  var i, table, thead, tbody, tr, th, td, a, ul, li;
  tbody = document.createElement('tbody');
  for (i = 1; i <= 1000; i++) {
    tr = document.createElement('tr');
    td = document.createElement('td');
    td.appendChild(document.createTextNode((i % 2) ? 'yes' : 'no'));
    tr.appendChild(td);
    td = document.createElement('td');
    td.appendChild(document.createTextNode(i));
    tr.appendChild(td);
    td = document.createElement('td');
    td.appendChild(document.createTextNode('my name is #' + i));
    tr.appendChild(td);
    a = document.createElement('a');
    a.setAttribute('href', 'http://example.org/' + i + '.html');
    a.appendChild(document.createTextNode('http://example.org/' + i + '.html'));
    td = document.createElement('td');
    td.appendChild(a);
    tr.appendChild(td);
    ul = document.createElement('ul');
    a = document.createElement('a');
    a.setAttribute('href', 'edit.php?id=' + i);
    a.appendChild(document.createTextNode('edit'));
    li = document.createElement('li');
    li.appendChild(a);
    ul.appendChild(li);
    a = document.createElement('a');
    a.setAttribute('href', 'delete.php?id=' + i);
    a.appendChild(document.createTextNode('delete'));
    li = document.createElement('li');
    li.appendChild(a);
    ul.appendChild(li);
    td = document.createElement('td');
    td.appendChild(ul);
    tr.appendChild(td);
    tbody.appendChild(tr);
  }
  tr = document.createElement('tr');
  th = document.createElement('th');
  th.appendChild(document.createTextNode('yes?'));
  tr.appendChild(th);
  th = document.createElement('th');
  th.appendChild(document.createTextNode('id'));
  tr.appendChild(th);
  th = document.createElement('th');
  th.appendChild(document.createTextNode('name'));
  tr.appendChild(th);
  th = document.createElement('th');
  th.appendChild(document.createTextNode('url'));
  tr.appendChild(th);
  th = document.createElement('th');
  th.appendChild(document.createTextNode('action'));
  tr.appendChild(th);
  thead = document.createElement('thead');
  thead.appendChild(tr);
  table = document.createElement('table');
  table.setAttribute('border', 1);
  table.setAttribute('width', '100%');
  table.appendChild(thead);
  table.appendChild(tbody);
  document.getElementById('here').appendChild(table);
};

 

    The results of generating the HTML table using innerHTML as compared to using pure DOM methods are shown in Figure 3-3. The benefits of innerHTML are more obvious in older browser versions (innerHTML is 3.6 times faster in IE6), but the benefits are less pronounced in newer versions. And in newer WebKit-based browsers it's the opposite: using DOM methods is slightly faster. So the decision about which approach to take will depend on the browsers your users are commonly using, as well as your coding preferences.

    使用innerHTML和纯DOM方法创建HTML表的比较结果参见图3-3。innerHTML的好处在老式浏览器上显而易见(在IE6中innerHTML比对手快3.6倍),但在新版本浏览器上就不那么明显了。而在最新的基于WebKit的浏览器上其结果正好相反:使用DOM方法更快。因此,决定采用哪种方法将取决于用户经常使用的浏览器,以及你的编码偏好。

Figure 3-3. The benefit of using innerHTML over DOM methods to create a 1,000-row table;
innerHTML is more than three times faster in IE6 and slightly slower in the latest WebKit browsers

图3-3  使用innerHTML和DOM方法创建一个1000行的表

在IE6中,innerHTML比对手快三倍,但在最新的基于WebKit的浏览器中慢于对手

 

    Using innerHTML will give you faster execution in most browsers in performance-critical operations that require updating a large part of the HTML page. But for most everyday cases there isn't a big difference, and so you should consider readability, maintenance, team preferences, and coding conventions when deciding on your approach.

    如果在一个性能苛刻的操作中更新一大块HTML页面,innerHTML在大多数浏览器中执行更快。但对于大多数日常操作而言,其差异并不大,所以你应当根据代码可读性,可维护性,团队习惯,代码风格来综合决定采用哪种方法。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值