[翻譯]High Performance JavaScript(010)

Cloning Nodes  節點克隆

 

    Another way of updating page contents using DOM methods is to clone existing DOM elements instead of creating new ones—in other words, using element.cloneNode() (where element is an existing node) instead of document.createElement().

    使用DOM方法更新頁面內容的另一個途徑是克隆已有DOM元素,而不是創建新的——即使用element.cloneNode()(element是一個已存在的節點)代替document.createElement();

 

    Cloning nodes is more efficient in most browsers, but not by a big margin. Regenerating the table from the previous example by creating the repeating elements only once and then copying them results in slightly faster execution times:

    在大多數瀏覽器上,克隆節點更有效率,但提高不太多。用克隆節點的辦法重新生成前面例子中的表,單元只創建一次,然後重複執行復制操作,這樣做只是稍微快了一點:

 

• 2% in IE8, but no change in IE6 and IE7

 在IE8中快2%,但在IE6和IE7中無變化


• Up to 5.5% in Firefox 3.5 and Safari 4

 在Firefox 3.5和Safari 4中快了5.5%


• 6% in Opera (but no savings in Opera 10)

 在Opera中快了6%(但是在Opera 10中無變化)


• 10% in Chrome 2 and 3% in Chrome 3

在Chrome 2中快了10%,在Chrome 3中快了3%

 

    As an illustration, here's a partial code listing for generating the table using element.cloneNode():

    一個示例,這裏是使用element.cloneNode()創建表的部分代碼:

 

function tableClonedDOM() {
  var i, table, thead, tbody, tr, th, td, a, ul, li,
  oth = document.createElement('th'),
  otd = document.createElement('td'),
  otr = document.createElement('tr'),
  oa = document.createElement('a'),
  oli = document.createElement('li'),
  oul = document.createElement('ul');
  tbody = document.createElement('tbody');
  for (i = 1; i <= 1000; i++) {
    tr = otr.cloneNode(false);
    td = otd.cloneNode(false);
    td.appendChild(document.createTextNode((i % 2) ? 'yes' : 'no'));
    tr.appendChild(td);
    td = otd.cloneNode(false);
    td.appendChild(document.createTextNode(i));
    tr.appendChild(td);
    td = otd.cloneNode(false);
    td.appendChild(document.createTextNode('my name is #' + i));
    tr.appendChild(td);
    // ... the rest of the loop ...
  }
  // ... the rest of the table generation ...
}

 

HTML Collections  HTML集合

 

    HTML collections are array-like objects containing DOM node references. Examples of collections are the values returned by the following methods:

    HTML集合是用於存放DOM節點引用的類數組對象。下列函數的返回值就是一個集合:

 

document.getElementsByName()
document.getElementsByClassName()
document.getElementsByTagName()

 

    The following properties also return HTML collections:

    下列屬性也屬於HTML集合:

 

document.images
    All img elements on the page

    頁面中所有的<img>元素

 

document.links
    All a elements

    所有的<a>元素

 

document.forms
    All forms

    所有表單

 

document.forms[0].elements
    All fields in the first form on the page

    頁面中第一個表單的所有字段

 

    These methods and properties return HTMLCollection objects, which are array-like lists. They are not arrays (because they don't have methods such as push() or slice()), but provide a length property just like arrays and allow indexed access to the elements in the list. For example, document.images[1] returns the second element in the collection. As defined in the DOM standard, HTML collections are "assumed to be live, meaning that they are automatically updated when the underlying document is updated" (seehttp://www.w3.org/TR/DOM-Level-2-HTML/html.html#ID-75708506).

    這些方法和屬性返回HTMLCollection對象,是一種類似數組的列表。它們不是數組(因爲它們沒有諸如push()或slice()之類的方法),但是提供了一個length屬性,和數組一樣你可以使用索引訪問列表中的元素。例如,document.images[1]返回集合中的第二個元素。正如DOM標準中所定義的那樣,HTML集合是一個“虛擬存在,意味着當底層文檔更新時,它們將自動更新”(參見http://www.w3.org/TR/DOM-Level-2-HTML/html.html#ID-75708506)。


    The HTML collections are in fact queries against the document, and these queries are being reexecuted every time you need up-to-date information, such as the number of elements in the collection (i.e., the collection's length). This could be a source of inefficiencies.

    HTML集合實際上在查詢文檔,當你更新信息時,每次都要重複執行這種查詢操作。例如讀取集合中元素的數目(也就是集合的length)。這正是低效率的來源。

 

Expensive collections  昂貴的集合

 

    To demonstrate that the collections are live, consider the following snippet:

    爲演示集合的存在性,考慮下列代碼段:

 

// an accidentally infinite loop
var alldivs = document.getElementsByTagName('div');
for (var i = 0; i < alldivs.length; i++) {
  document.body.appendChild(document.createElement('div'))
}

    This code looks like it simply doubles the number of div elements on the page. It loops through the existing divs and creates a new div every time, appending it to the body. But this is in fact an infinite loop because the loop's exit condition, alldivs.length, increases by one with every iteration, reflecting the current state of the underlying document.

    這段代碼看上去只是簡單地倍增了頁面中div元素的數量。它遍歷現有div,每次創建一個新的div並附加到body上面。但實際上這是個死循環,因爲循環終止條件alldivs.length在每次迭代中都會增加,它反映出底層文檔的當前狀態。

 

    Looping through HTML collections like this may lead to logic mistakes, but it's also slower, due to the fact that the query needs to run on every iteration (see Figure 3-4).

    像這樣遍歷HTML集合會導致邏輯錯誤,而且也很慢,因爲每次迭代都進行查詢(如圖3-4)。

Figure 3-4. Looping over an array is significantly faster than looping through an HTML collection of the same size and content

圖3-4  遍歷數組明顯快於同樣大小和內容的HTML集合

 

    As discussed in Chapter 4, accessing an array's length property in loop control conditions is not recommended. Accessing a collection's length is even slower than accessing a regular array's length because it means rerunning the query every time. This is demonstrated by the following example, which takes a collection coll, copies it into an array arr, and then compares how much time it takes to iterate through each.

    正如在第四章中將要討論的,不建議用數組的length屬性做循環判斷條件。訪問集合的length比數組的length還要慢,因爲它意味着每次都要重新運行查詢過程。在下面的例子中,將一個集合coll拷貝到數組arr中,然後比較每次迭代所用的時間。

 

    Consider a function that copies an HTML collection into a regular array:

    考慮這個函數,它將一個HTML集合拷貝給一個常規數組:

 

function toArray(coll) {
  for (var i = 0, a = [], len = coll.length; i < len; i++) {
    a[i] = coll[i];
  }
  return a;
}

 

    And setting up a collection and a copy of it into an array:

    設置一個集合,並把它拷貝到一個數組:

 

var coll = document.getElementsByTagName('div');
var ar = toArray(coll);

 

    The two functions to compare would be:

    比較下列兩個函數:

 

//slower
function loopCollection() {
  for (var count = 0; count < coll.length; count++) {
   
  }
}
// faster
function loopCopiedArray() {
  for (var count = 0; count < arr.length; count++) {
   
  }
}

    When the length of the collection is accessed on every iteration, it causes the collection to be updated and has a significant performance penalty across all browsers. The way to optimize this is to simply cache the length of the collection into a variable and use this variable to compare in the loop's exit condition:

    當每次迭代過程訪問集合的length屬性時,它導致集合器更新,在所有瀏覽器上都會產生明顯的性能損失。優化的辦法很簡單,只要將集合的length屬性緩存到一個變量中,然後在循環判斷條件中使用這個變量:

 

function loopCacheLengthCollection() {
  var coll = document.getElementsByTagName('div'),
  len = coll.length;
  for (var count = 0; count < len; count++) {
   
  }
}

    This function will run about as fast as loopCopiedArray().

    此函數運行得與loopCopiedArray()一樣快。

 

    For many use cases that require a single loop over a relatively small collection, just caching the length of the collection is good enough. But looping over an array is faster that looping over a collection, so if the elements of the collection are copied into an array first, accessing their properties is faster. Keep in mind that this comes at the price of an extra step and another loop over the collection, so it's important to profile and decide whether using an array copy will be beneficial in your specific case.

    許多用例需要對一個相關的小集合進行遍歷,只要將length緩存一下就足夠好了。但是遍歷數組比遍歷集合快,如果先將集合元素拷貝到數組,訪問它們的屬性將更快。請記住這需要一個額外的步驟,要遍歷集合,所以應當評估在特定條件下使用這樣一個數組副本是否有益。

 

    Consult the function toArray() shown earlier for an example of a generic collection-to-array function.

    前面提到的toArray()函數可認爲是一個通用的集合轉數組函數。

 

Local variables when accessing collection elements  訪問集合元素時使用局部變量

 

    The previous example used just an empty loop, but what happens when the elements of the collection are accessed within the loop?

    前面的例子使用了一個空循環,如果在循環中訪問集合元素,會發生什麼?

 

    In general, for any type of DOM access it's best to use a local variable when the same DOM property or method is accessed more than once. When looping over a collection, the first optimization is to store the collection in a local variable and cache the length outside the loop, and then use a local variable inside the loop for elements that are accessed more than once.

    一般來說,對於任何類型的DOM訪問,如果同一個DOM屬性或方法被訪問一次以上,最好使用一個局部變量緩存此DOM成員。當遍歷一個集合時,第一個優化是將集合引用存儲於局部變量,並在循環之外緩存length屬性。然後,如果在循環體中多次訪問同一個集合元素,那麼使用局部變量緩存它。

 

    In the next example, three properties of each element are accessed within the loop. The slowest version accesses the global document every time, an optimized version caches a reference to the collection, and the fastest version also stores the current element of the collection into a variable. All three versions cache the length of the collection.

    在下面的例子中,在循環中訪問每個元素的三個屬性。最慢的版本每次都要訪問全局的document,優化後的版本緩存了一個指向集合的引用,最快的版本將集合的當前元素存入局部變量。所有三個版本都緩存了集合的length屬性。

 

// slow
function collectionGlobal() {
  var coll = document.getElementsByTagName('div'),
  len = coll.length,
  name = '';
  for (var count = 0; count < len; count++) {
    name = document.getElementsByTagName('div')[count].nodeName;
    name = document.getElementsByTagName('div')[count].nodeType;
    name = document.getElementsByTagName('div')[count].tagName;
  }
  return name;
};
// faster
function collectionLocal() {
  var coll = document.getElementsByTagName('div'),
  len = coll.length,
  name = '';
  for (var count = 0; count < len; count++) {
    name = coll[count].nodeName;
    name = coll[count].nodeType;
    name = coll[count].tagName;
  }
  return name;
};
// fastest
function collectionNodesLocal() {
  var coll = document.getElementsByTagName('div'),
  len = coll.length,
  name = '',
  el = null;
  for (var count = 0; count < len; count++) {
    el = coll[count];
    name = el.nodeName;
    name = el.nodeType;
    name = el.tagName;
  }
  return name;
};

 

    Figure 3-5 shows the benefits of optimizing collection loops. The first bar plots how many times faster it is to access the collection through a local reference, and the second bar shows that there's additional benefit to caching collection items when they are accessed multiple times.

    圖3-5顯示了優化集合循環的好處。第一條柱形圖標出通過局部引用訪問集合帶來的速度提升,第二條柱形圖顯示出多次訪問時緩衝集合項帶來的速度提升。

Figure 3-5. Benefit of using local variables to store references to a collection and its elements during loops

圖3-5  在循環中使用局部變量緩存集合引用和集合元素帶來的速度提升

 

Walking the DOM  DOM漫談

 

    The DOM API provides multiple avenues to access specific parts of the overall document structure. In cases when you can choose between approaches, it's beneficial to use the most efficient API for a specific job.

    DOM API提供了多種途徑訪問整個文檔結構的特定部分。當你在多種可行方法之間進行選擇時,最好針對特定操作選擇最有效的API。

 

Crawling the DOM  抓取DOM

 

    Often you need to start from a DOM element and work with the surrounding elements, maybe recursively iterating over all children. You can do so by using the childNodes collection or by getting each element's sibling using nextSibling.

    你經常需要從一個DOM元素開始,操作周圍的元素,或者遞歸迭代所有的子節點。你可以使用childNode集合或者使用nextSibling獲得每個元素的兄弟節點。

 

    Consider these two equivalent approaches to a nonrecursive visit of an element's children:

    考慮這兩個同樣功能的例子,採用非遞歸方式遍歷一個元素的子節點:

 

function testNextSibling() {
  var el = document.getElementById('mydiv'),
  ch = el.firstChild,
  name = '';
  do {
    name = ch.nodeName;
  } while (ch = ch.nextSibling);
  return name;
};
function testChildNodes() {
  var el = document.getElementById('mydiv'),
  ch = el.childNodes,
  len = ch.length,
  name = '';
  for (var count = 0; count < len; count++) {
    name = ch[count].nodeName;
  }
  return name;
};

    Bear in mind that childNodes is a collection and should be approached carefully, caching the length in loops so it's not updated on every iteration.

    記住,childNodes是一個集合,要小心處理,在循環中緩存length屬性所以不會在每次迭代中更新。

 

    The two approaches are mostly equal in terms of execution time across browsers. But in IE, nextSibling performs much better than childNodes. In IE6, nextSibling is 16 times faster, and in IE7 it's 105 times faster. Given these results, using nextSibling is the preferred method of crawling the DOM in older IE versions in performance-critical cases. In all other cases, it's mostly a question of personal and team preference.

    在不同瀏覽器上,這兩種方法的運行時間基本相等。但是在IE中,nextSibling表現得比childNode好。在IE6中,nextSibling比對手快16倍,而在IE7中快樂105倍。鑑於這些結果,在老的IE中性能嚴苛的使用條件下,用nextSibling抓取DOM是首選方法。在其他情況下,主要看個人和團隊偏好。

 

Element nodes  元素節點

 

    DOM properties such as childNodes, firstChild, and nextSibling don't distinguish between element nodes and other node types, such as comments and text nodes (which are often just spaces between two tags). In many cases, only the element nodes need to be accessed, so in a loop it's likely that the code needs to check the type of node returned and filter out nonelement nodes. This type checking and filtering is unnecessary DOM work.

    DOM屬性諸如childNode,firstChild,和nextSibling不區分元素節點和其他類型節點,如註釋節點和文本節點(這兩個標籤之間往往只是一些空格)。在許多情況下,只有元素節點需要被訪問,所以在循環中,似乎應當對節點返回類型進行檢查,過濾出非元素節點。這些檢查和過濾都是不必要的DOM操作。

 

    Many modern browsers offer APIs that only return element nodes. It's better to use those when available, because they'll be faster than if you do the filtering yourself in JavaScript. Table 3-1 lists those convenient DOM properties.

    許多現代瀏覽器提供了API函數只返回元素節點。如果可用最好利用起來,因爲它們比你自己在JavaScript中寫的過濾方法要快。表3-1列出這些便利的DOM屬性。

 

Table 3-1. DOM properties that distinguish element nodes (HTML tags) versus all nodes

表3-1  只表示元素節點的DOM屬性(HTML標籤)和表示所有節點的屬性

 

    All of the properties listed in Table 3-1 are supported as of Firefox 3.5, Safari 4, Chrome 2, and Opera 9.62. Of these properties, IE versions 6, 7, and 8 only support children.

    表3-1中列舉的所有屬性能夠被Firefox 3.5,Safari 4,Chrome 2,和Opera 9.62支持。所有這些屬性中,IE6,7,8只支持children。

 

    Looping over children instead of childNodes is faster because there are usually less items to loop over. Whitespaces in the HTML source code are actually text nodes, and they are not included in the children collection. children is faster than childNodes across all browsers, although usually not by a big margin—1.5 to 3 times faster. One notable exception is IE, where iterating over the children collection is significantly faster than iterating over childNodes—24 times faster in IE6 and 124 times faster in IE7.

    遍歷children比childNodes更快,因爲集合項更少。HTML源碼中的空格實際上是文本節點,它們不包括在children集合中。在所有瀏覽器中children比childNodes更快,雖然差別不是太大,通常快1.5到3倍。特別值得注意的是IE,遍歷children明顯快於遍歷childNodes——在IE6中快24倍,在IE7中快124倍。

 

The Selectors API  選擇器API

 

    When identifying the elements in the DOM to work with, developers often need finer control than methods such as getElementById() and getElementsByTagName() can provide. Sometimes you combine these calls and iterate over the returned nodes in order to get to the list of elements you need, but this refinement process can become inefficient.

    識別DOM中的元素時,開發者經常需要更精細的控制,而不僅是getElementById()和getElementsByTagName()之類的函數。有時你結合這些函數調用並迭代操作它們返回的節點,以獲取所需要的元素,這一精細的過程可能造成效率低下。

 

    On the other hand, using CSS selectors is a convenient way to identify nodes because developers are already familiar with CSS. Many JavaScript libraries have provided APIs for that purpose, and now recent browser versions provide a method called querySelectorAll() as a native browser DOM method. Naturally this approach is faster than using JavaScript and DOM to iterate and narrow down a list of elements.

    另一方面,使用CSS選擇器是一個便捷的確定節點的方法,因爲開發者已經對CSS很熟悉了。許多JavaScript庫爲此提供了API,而且最新的瀏覽器提供了一個名爲querySelectorAll()的原生瀏覽器DOM函數。顯然這種方法比使用JavaScript和DOM迭代並縮小元素列表的方法要快。

 

    Consider the following:

    考慮下列代碼:

 

var elements = document.querySelectorAll('#menu a');

    The value of elements will contain a list of references to all a elements found inside an element with id="menu". The method querySelectorAll() takes a CSS selector string as an argument and returns a NodeList—an array-like object containing matching nodes. The method doesn't return an HTML collection, so the returned nodes do not represent the live structure of the document. This avoids the performance (and potentially logic) issues with HTML collection discussed previously in this chapter.

    elements的值將包含一個引用列表,指向那些具有id="menu"屬性的元素。函數querySelectorAll()接收一個CSS選擇器字符串參數並返回一個NodeList——由符合條件的節點構成的類數組對象。此函數不返回HTML集合,所以返回的節點不呈現文檔的“存在性結構”。這就避免了本章前面提到的HTML集合所固有的性能問題(以及潛在的邏輯問題)。

 

    To achieve the same goal as the preceding code without using querySelectorAll(), you will need the more verbose:

    如果不使用querySelectorAll(),達到同樣的目標的代碼會冗長一些:

 

var elements = document.getElementById('menu').getElementsByTagName('a');

    In this case elements will be an HTML collection, so you'll also need to copy it into an array if you want the exact same type of static list as returned by querySelectorAll().

    這種情況下elements將是一個HTML集合,所以你還需要將它拷貝到一個數組中,如果你想得到與querySelectorAll()同樣的返回值類型的話。

 

    Using querySelectorAll() is even more convenient when you need to work with a union of several queries. For example, if the page has some div elements with a class name of "warning" and some with a class of "notice", to get a list of all of them you can use querySelectorAll():

    當你需要聯合查詢時,使用querySelectorAll()更加便利。例如,如果頁面中有些div元素的class名稱是"warning",另一些class名是"notice",你可以用querySelectorAll()一次性獲得這兩類節點。

 

var errs = document.querySelectorAll('div.warning, div.notice');

    Getting the same list without querySelectorAll() is considerably more work. One way is to select all div elements and iterate through them to filter out the ones you don't need.

    如果不使用querySelectorAll(),獲得同樣列表需要更多工作。一個辦法是選擇所有的div元素,然後通過迭代操作過濾出那些不需要的單元。

 

var errs = [],
divs = document.getElementsByTagName('div'),
classname = '';
for (var i = 0, len = divs.length; i < len; i++) {
  classname = divs[i].className;
  if (classname === 'notice' || classname === 'warning') {
    errs.push(divs[i]);
  }
}

    Comparing the two pieces of code shows that using the Selectors API is 2 to 6 times faster across browsers (Figure 3-6).

    比較這兩段代碼,使用選擇器API比對手快了2~6倍(圖3-6)。

Figure 3-6. The benefit of using the Selectors API over iterating instead of the results of
getElementsByTagName()

圖3-6  使用選擇器API和使用getElementsByTagName()的性能對比

 

    The Selectors API is supported natively in browsers as of these versions: Internet Explorer
8, Firefox 3.5, Safari 3.1, Chrome 1, and Opera 10.

    下列瀏覽器支持選擇器API:Internet Explorer 8,Firefox 3.5,Safari 3.1,Chrome 1,Opera 10。

 

    As the results in the figure show, it's a good idea to check for support for document.querySelectorAll() and use it when available. Also, if you're using a selector API provided by a JavaScript library, make sure the library uses the native API under the hood. If not, you probably just need to upgrade the library version.

    正如圖中顯示的那樣,如果瀏覽器支持document.querySelectorAll(),那麼最好使用它。如果你使用JavaScript庫所提供的選擇器API,確認一下該庫是否確實使用了原生方法。如果不是,你大概需要將庫升級到新版本。

 

    You can also take advantage of another method called querySelector(), a convenient
method that returns only the first node matched by the query.

    你還可以從另一個函數querySelector()獲益,這個便利的函數只返回符合查詢條件的第一個節點。

 

    These two methods are properties of the DOM nodes, so you can use document.querySelector('.myclass') to query nodes in the whole document, or you can query a subtree using elref.querySelector('.myclass'), where elref is a reference to a DOM element.

    這兩個函數都是DOM節點的屬性,所以你可以使用document.querySelector('.myclass')來查詢整個文檔中的節點,或者使用elref.querySelector('.myclass')在子樹中進行查詢,其中elref是一個DOM元素的引用。

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