[翻譯]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在大多數瀏覽器中執行更快。但對於大多數日常操作而言,其差異並不大,所以你應當根據代碼可讀性,可維護性,團隊習慣,代碼風格來綜合決定採用哪種方法。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章