[翻译]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元素的引用。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值