加载中…

加载中...

个人资料
青岛刘新
青岛刘新 新浪个人认证
  • 博客等级:
  • 博客积分:0
  • 博客访问:249,334
  • 关注人气:127
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
相关博文
推荐博文
谁看过这篇博文
加载中…
正文 字体大小:

[翻译]High Performance JavaScript(011)

(2010-04-30 00:00:00)
标签:

high

performance

javascript

分类: 软件技术

Repaints and Reflows  重绘和重排版

 

    Once the browser has downloaded all the components of a page—HTML markup, JavaScript, CSS, images—it parses through the files and creates two internal data structures:

    当浏览器下载完所有页面HTML标记,JavaScript,CSS,图片之后,它解析文件并创建两个内部数据结构:

 

A DOM tree
    A representation of the page structure

一棵DOM树

    表示页面结构

 

A render tree
    A representation of how the DOM nodes will be displayed

一棵渲染树

    表示DOM节点如何显示

 

    The render tree has at least one node for every node of the DOM tree that needs to be displayed (hidden DOM elements don't have a corresponding node in the render tree). Nodes in the render tree are called frames or boxes in accordance with the CSS model that treats page elements as boxes with padding, margins, borders, and position. Once the DOM and the render trees are constructed, the browser can display ("paint") the elements on the page.

    渲染树中为每个需要显示的DOM树节点存放至少一个节点(隐藏DOM元素在渲染树中没有对应节点)。渲染树上的节点称为“框”或者“盒”,符合CSS模型的定义,将页面元素看作一个具有填充、边距、边框和位置的盒。一旦DOM树和渲染树构造完毕,浏览器就可以显示(绘制)页面上的元素了。

 

    When a DOM change affects the geometry of an element (width and height)—such as a change in the thickness of the border or adding more text to a paragraph, resulting in an additional line—the browser needs to recalculate the geometry of the element as well as the geometry and position of other elements that could have been affected by the change. The browser invalidates the part of the render tree that was affected by the change and reconstructs the render tree. This process is known as a reflow. Once the reflow is complete, the browser redraws the affected parts of the screen in a process called repaint.

    当DOM改变影响到元素的几何属性(宽和高)——例如改变了边框宽度或在段落中添加文字,将发生一系列后续动作——浏览器需要重新计算元素的几何属性,而且其他元素的几何属性和位置也会因此改变受到影响。浏览器使渲染树上受到影响的部分失效,然后重构渲染树。这个过程被称作重排版。重排版完成时,浏览器在一个重绘进程中重新绘制屏幕上受影响的部分。

 

    Not all DOM changes affect the geometry. For example, changing the background color of an element won't change its width or height. In this case, there is a repaint only (no reflow), because the layout of the element hasn't changed.

    不是所有的DOM改变都会影响几何属性。例如,改变一个元素的背景颜色不会影响它的宽度或高度。在这种情况下,只需要重绘(不需要重排版),因为元素的布局没有改变。

 

    Repaints and reflows are expensive operations and can make the UI of a web application less responsive. As such, it's important to reduce their occurrences whenever possible.

    重绘和重排版是负担很重的操作,可能导致网页应用的用户界面失去相应。所以,十分有必要尽可能减少这类事情的发生。

 

When Does a Reflow Happen?  重排版时会发生什么?

 

    As mentioned earlier, a reflow is needed whenever layout and geometry change. This happens when:

    正如前面所提到的,当布局和几何改变时需要重排版。在下述情况中会发生重排版:


• Visible DOM elements are added or removed

  添加或删除可见的DOM元素


• Elements change position

  元素位置改变


• Elements change size (because of a change in margin, padding, border thickness, width, height, etc.)

  元素尺寸改变(因为边距,填充,边框宽度,宽度,高度等属性改变)


• Content is changed, e.g., text changes or an image is replaced with one of a different size

  内容改变,例如,文本改变或图片被另一个不同尺寸的所替代


• Page renders initially

  最初的页面渲染


• Browser window is resized

  浏览器窗口改变尺寸


    Depending on the nature of the change, a smaller or bigger part of the render tree needs to be recalculated. Some changes may cause a reflow of the whole page: for example, when a scroll bar appears.

    根据改变的性质,渲染树上或大或小的一部分需要重新计算。某些改变可导致重排版整个页面:例如,当一个滚动条出现时。

 

Queuing and Flushing Render Tree Changes  查询并刷新渲染树改变

 

    Because of the computation costs associated with each reflow, most browsers optimize the reflow process by queuing changes and performing them in batches. However, you may (often involuntarily) force the queue to be flushed and require that all scheduled changes be applied right away. Flushing the queue happens when you want to retrieve layout information, which means using any of the following:

    因为计算量与每次重排版有关,大多数浏览器通过队列化修改和批量显示优化重排版过程。然而,你可能(经常不由自主地)强迫队列刷新并要求所有计划改变的部分立刻应用。获取布局信息的操作将导致刷新队列动作,这意味着使用了下面这些方法:

 

offsetTop, offsetLeft, offsetWidth, offsetHeight
scrollTop, scrollLeft, scrollWidth, scrollHeight
clientTop, clientLeft, clientWidth, clientHeight
getComputedStyle() (currentStyle in IE)(在IE中此函数称为currentStyle

 

    The layout information returned by these properties and methods needs to be up to date, and so the browser has to execute the pending changes in the rendering queue and reflow in order to return the correct values.

    布局信息由这些属性和方法返回最新的数据,所以浏览器不得不运行渲染队列中待改变的项目并重新排版以返回正确的值。

 

    During the process of changing styles, it's best not to use any of the properties shown in the preceding list. All of these will flush the render queue, even in cases where you're retrieving layout information that wasn't recently changed or isn't even relevant to the latest changes.

    在改变风格的过程中,最好不要使用前面列出的那些属性。任何一个访问都将刷新渲染队列,即使你正在获取那些最近未发生改变的或者与最新的改变无关的布局信息。

 

    Consider the following example of changing the same style property three times (this is probably not something you'll see in real code, but is an isolated illustration of an important topic):

    考虑下面这个例子,它改变同一个风格属性三次(这也许不是你在真正的代码中所见到的,不过它孤立地展示出一个重要话题):

 

// setting and retrieving styles in succession
var computed,
    tmp = '',
    bodystyle = document.body.style;
if (document.body.currentStyle) { // IE, Opera
  computed = document.body.currentStyle;
} else { // W3C
  computed = document.defaultView.getComputedStyle(document.body, '');
}
// inefficient way of modifying the same property
// and retrieving style information right after

bodystyle.color = 'red';
tmp = computed.backgroundColor;
bodystyle.color = 'white';
tmp = computed.backgroundImage;
bodystyle.color = 'green';
tmp = computed.backgroundAttachment;

    In this example, the foreground color of the body element is being changed three times, and after every change, a computed style property is retrieved. The retrieved properties—backgroundColor, backgroundImage, and backgroundAttachment—are unrelated to the color being changed. Yet the browser needs to flush the render queue and reflow due to the fact that a computed style property was requested.

    在这个例子中,body元素的前景色被改变了三次,每次改变之后,都导入computed的风格。导入的属性backgroundColor, backgroundImage, 和backgroundAttachment与颜色改变无关。然而,浏览器需要刷新渲染队列并重排版,因为computed的风格被查询而引发。

 

    A better approach than this inefficient example is to never request layout information while it's being changed. If the computed style retrieval is moved to the end, the code looks like this:

    比这个不讲效率的例子更好的方法是不要在布局信息改变时查询它。如果将查询computed风格的代码搬到末尾,代码看起来将是这个样子:

 

bodystyle.color = 'red';
bodystyle.color = 'white';
bodystyle.color = 'green';
tmp = computed.backgroundColor;
tmp = computed.backgroundImage;
tmp = computed.backgroundAttachment;

    The second example will be faster across all browsers, as seen in Figure 3-7.

    在所有浏览器上,第二个例子将更快,如图3-7所示。

 

[翻译]High <wbr>Performance <wbr>JavaScript(011)

Figure 3-7. Benefit of preventing reflows by delaying access to layout information

图3-7  通过延迟访问布局信息避免重排版而带来的性能提升

 

Minimizing Repaints and Reflows  最小化重绘和重排版

 

    Reflows and repaints can be expensive, and therefore a good strategy for responsive applications is to reduce their number. In order to minimize this number, you should combine multiple DOM and style changes into a batch and apply them once.

    重排版和重绘代价昂贵,所以,提高程序响应速度一个好策略是减少此类操作发生的机会。为减少发生次数,你应该将多个DOM和风格改变合并到一个批次中一次性执行。

 

Style changes  改变风格

 

    Consider this example:

    考虑这个例子:

 

var el = document.getElementByIdx('mydiv');
el.style.borderLeft = '1px';
el.style.borderRight = '2px';
el.style.padding = '5px';

 

    Here there are three style properties being changed, each of them affecting the geometry of the element. In the worst case, this will cause the browser to reflow three times. Most modern browsers optimize for such cases and reflow only once, but it can still be inefficient in older browsers or if there's a separate asynchronous process happening at the same time (i.e., using a timer). If other code is requesting layout information while this code is running, it could cause up to three reflows. Also, the code is touching the DOM four times and can be optimized.

    这里改变了三个风格属性,每次改变都影响到元素的几何属性。在这个糟糕的例子中,它导致浏览器重排版了三次。大多数现代浏览器优化了这种情况只进行一次重排版,但是在老式浏览器中,或者同时有一个分离的同步进程(例如使用了一个定时器),效率将十分低下。如果其他代码在这段代码运行时查询布局信息,将导致三次重布局发生。而且,此代码访问DOM四次,可以被优化。

 

    A more efficient way to achieve the same result is to combine all the changes and apply them at once, modifying the DOM only once. This can be done using the cssText property:

    一个达到同样效果而效率更高的方法是:将所有改变合并在一起执行,只修改DOM一次。可通过使用cssText属性实现:

 

var el = document.getElementByIdx('mydiv');
el.style.cssText = 'border-left: 1px; border-right: 2px; padding: 5px;';

    Modifying the cssText property as shown in the example overwrites existing style information, so if you want to keep the existing styles, you can append this to the cssText string:

    这个例子中的代码修改cssText属性,覆盖已存在的风格信息。如果你打算保持当前的风格,你可以将它附加在cssText字符串的后面。

 

el.style.cssText += '; border-left: 1px;';

    Another way to apply style changes only once is to change the CSS class name instead of changing the inline styles. This approach is applicable in cases when the styles do not depend on runtime logic and calculations. Changing the CSS class name is cleaner and more maintainable; it helps keep your scripts free of presentation code, although it might come with a slight performance hit because the cascade needs to be checked when changing classes.

    另一个一次性改变风格的办法是修改CSS的类名称,而不是修改内联风格代码。这种方法适用于那些风格不依赖于运行逻辑,不需要计算的情况。改变CSS类名称更清晰,更易于维护;它有助于保持脚本免除显示代码,虽然它可能带来轻微的性能冲击,因为改变类时需要检查级联表。

 

var el = document.getElementByIdx('mydiv');
el.className = 'active';

 

Batching DOM changes  批量修改DOM

 

    When you have a number of changes to apply to a DOM element, you can reduce the number of repaints and reflows by following these steps:

    当你需要对DOM元素进行多次修改时,你可以通过以下步骤减少重绘和重排版的次数:

 

1. Take the element off of the document flow.

   从文档流中摘除该元素


2. Apply multiple changes.

   对其应用多重改变


3. Bring the element back to the document.

   将元素带回文档中

 

    This process causes two reflows—one at step 1 and one at step 3. If you omit those steps, every change you make in step 2 could cause its own reflows.

    此过程引发两次重排版——第一步引发一次,第三步引发一次。如果你忽略了这两个步骤,那么第二步中每次改变都将引发一次重排版。

 

    There are three basic ways to modify the DOM off the document:

    有三种基本方法可以将DOM从文档中摘除:

 

• Hide the element, apply changes, and show it again.

  隐藏元素,进行修改,然后再显示它。


• Use a document fragment to build a subtree outside of the live DOM and then copy it to the document.

  使用一个文档片断在已存DOM之外创建一个子树,然后将它拷贝到文档中。


• Copy the original element into an off-document node, modify the copy, and then replace the original element once you're done.

  将原始元素拷贝到一个脱离文档的节点中,修改副本,然后覆盖原始元素。

 

    To illustrate the off-document manipulations, consider a list of links that must be updated with more information:

    为演示脱离文档操作,考虑这样一个链接列表,它必须被更多的信息所更新:

 

<ul id="mylist">
  <li><a href="http://phpied.com">Stoyan</a></li>
  <li><a href="http://julienlecomte.com">Julien</a></li>
</ul>

    Suppose additional data, already contained in an object, needs to be inserted into this list. The data is defined as:

    假设附加数据已经存储在一个对象中了,需要插入到这个列表中。这些数据定义如下:

 

var data = [
  {
    "name": "Nicholas",
    "url":  "
http://nczonline.net"
  },
  {
    "name": "Ross",
    "url":  "
http://techfoolery.com"
  }
];

    The following is a generic function to update a given node with new data:

    下面是一个通用的函数,用于将新数据更新到指定节点中:

 

function appendDataToElement(appendToElement, data) {
  var a, li;
  for (var i = 0, max = data.length; i < max; i++) {
    a = document.createElement_x('a');
    a.href = data[i].url;
    a.appendChild(document.createTextNode(data[i].name));
    li = document.createElement_x('li');
    li.appendChild(a);
    appendToElement.appendChild(li);
  }
};

    The most obvious way to update the list with the data without worrying about reflows would be the following:

    将数据更新到列表而不管重排版问题,最明显的方法如下:

 

var ul = document.getElementByIdx('mylist');
appendDataToElement(ul, data);

    Using this approach, however, every new entry from the data array will be appended to the live DOM tree and cause a reflow. As discussed previously, one way to reduce reflows is to temporarily remove the <ul> element from the document flow by changing the display property and then revert it:

    使用这个方法,然而,data队列上的每个新条目追加到DOM树都会导致重排版。如前面所讨论过的,减少重排版的一个方法是通过改变display属性,临时从文档上移除<ul>元素然后再恢复它。

 

var ul = document.getElementByIdx('mylist');
ul.style.display = 'none';
appendDataToElement(ul, data);
ul.style.display = 'block';

    Another way to minimize the number of reflows is to create and update a document fragment, completely off the document, and then append it to the original list. A document fragment is a lightweight version of the document object, and it's designed to help with exactly this type of task—updating and moving nodes around. One syntactically convenient feature of the document fragments is that when you append a fragment to a node, the fragment's children actually get appended, not the fragment itself. The following solution takes one less line of code, causes only one reflow, and touches the live DOM only once:

    另一种减少重排版次数的方法是:在文档之外创建并更新一个文档片断,然后将它附加在原始列表上。文档片断是一个轻量级的document对象,它被设计专用于更新、移动节点之类的任务。文档片断一个便利的语法特性是当你向节点附加一个片断时,实际添加的是文档片断的子节点群,而不是片断自己。下面的例子减少一行代码,只引发一次重排版,只触发“存在DOM”一次。

 

var fragment = document.createDocumentFragment();
appendDataToElement(fragment, data);
document.getElementByIdx('mylist').appendChild(fragment);

    A third solution would be to create a copy of the node you want to update, work on the copy, and then, once you're done, replace the old node with the newly updated copy:

    第三种解决方法首先创建要更新节点的副本,然后在副本上操作,最后用新节点覆盖老节点:

 

var old = document.getElementByIdx('mylist');
var clone = old.cloneNode(true);
appendDataToElement(clone, data);
old.parentNode.replaceChild(clone, old);

    The recommendation is to use document fragments (the second solution) whenever possible because they involve the least amount of DOM manipulations and reflows. The only potential drawback is that the practice of using document fragments is currently underused and some team members may not be familiar with the technique.

    推荐尽可能使用文档片断(第二种解决方案)因为它涉及最少数量的DOM操作和重排版。唯一潜在的缺点是,当前文档片断还没有得到充分利用,开发者可能不熟悉此技术。

 

Caching Layout Information  缓冲布局信息

 

    As already mentioned, browsers try to minimize the number of reflows by queuing changes and executing them in batches. But when you request layout information such as offsets, scroll values, or computed style values, the browser flushes the queue and applies all the changes in order to return the updated value. It is best to minimize the number of requests for layout information, and when you do request it, assign it to local variables and work with the local values.

    浏览器通过队列化修改和批量运行的方法,尽量减少重排版次数。当你查询布局信息如偏移量、滚动条位置,或风格属性时,浏览器刷队列并执行所有修改操作,以返回最新的数值。最好是尽量减少对布局信息的查询次数,查询时将它赋给局部变量,并用局部变量参与计算。

 

    Consider an example of moving an element myElement diagonally, one pixel at a time, starting from position 100 × 100px and ending at 500 × 500px. In the body of a timeout loop you could use:

    考虑一个例子,将元素myElement向右下方向平移,每次一个像素,起始于100x100位置,结束于500x500位置,在timeout循环体中你可以使用:

 

// inefficient
myElement.style.left = 1 + myElement.offsetLeft + 'px';
myElement.style.top = 1 + myElement.offsetTop + 'px';
if (myElement.offsetLeft >= 500) {
  stopAnimation();
}

    This is not efficient, though, because every time the element moves, the code requests the offset values, causing the browser to flush the rendering queue and not benefit from its optimizations. A better way to do the same thing is to take the start value position once and assign it to a variable such as var current = myElement.offsetLeft;. Then, inside of the animation loop, work with the current variable and don't request offsets:

    这样做很没效率,因为每次元素移动,代码查询偏移量,导致浏览器刷新渲染队列,并没有从优化中获益。另一个办法只需要获得起始位置值一次,将它存入局部变量中var current = myElement.offsetLeft;。然后,在动画循环中,使用current变量而不再查询偏移量:

 

current++
myElement.style.left = current + 'px';
myElement.style.top = current + 'px';
if (current >= 500) {
  stopAnimation();
}

 

Take Elements Out of the Flow for Animations  将元素提出动画流

 

    Showing and hiding parts of a page in an expand/collapse manner is a common interaction pattern. It often includes geometry animation of the area being expanded, which pushes down the rest of the content on the page.

    显示和隐藏部分页面构成展开/折叠动画是一种常见的交互模式。它通常包括区域扩大的几何动画,将页面其他部分推向下方。

 

    Reflows sometimes affect only a small part of the render tree, but they can affect a larger portion, or even the whole tree. The less the browser needs to reflow, the more responsive your application will be. So when an animation at the top of the page pushes down almost the whole page, this will cause a big reflow and can be expensive, appearing choppy to the user. The more nodes in the render tree that need recalculation, the worse it becomes.

    重排版有时只影响渲染树的一小部分,但也可以影响很大的一部分,甚至整个渲染树。浏览器需要重排版的部分越小,应用程序的响应速度就越快。所以当一个页面顶部的动画推移了差不多整个页面时,将引发巨大的重排版动作,使用户感到动画卡顿。渲染树的大多数节点需要被重新计算,它变得更糟糕。

 

    A technique to avoid a reflow of a big part of the page is to use the following steps:

    使用以下步骤可以避免对大部分页面进行重排版:

 

1. Use absolute positioning for the element you want to animate on the page, taking it out of the layout flow of the page.

   使用绝对坐标定位页面动画的元素,使它位于页面布局流之外。


2. Animate the element. When it expands, it will temporarily cover part of the page. This is a repaint, but only of a small part of the page instead of a reflow and repaint of a big page chunk.

   启动元素动画。当它扩大时,它临时覆盖部分页面。这是一个重绘过程,但只影响页面的一小部分,避免重排版并重绘一大块页面。


3. When the animation is done, restore the positioning, thereby pushing down the rest of the document only once.

   当动画结束时,重新定位,从而只一次下移文档其他元素的位置。

 

译者注:文字描述比较简单概要,我对这三步的理解如下:

1、页面顶部可以“折叠/展开”的元素称作“动画元素”,用绝对坐标对它进行定位,当它的尺寸改变时,就不会推移页面中其他元素的位置,而只是覆盖其他元素。

2、展开动作只在“动画元素”上进行。这时其他元素的坐标并没有改变,换句话说,其他元素并没有因为“动画元素”的扩大而随之下移,而是任由动画元素覆盖。

3、“动画元素”的动画结束时,将其他元素的位置下移到动画元素下方,界面“跳”了一下。

 

IE and :hover  IE和:hover

 

    Since version 7, IE can apply the :hover CSS pseudo-selector on any element (in strict mode). However, if you have a significant number of elements with a :hover, the responsiveness degrades. The problem is even more visible in IE 8.

    自从版本7之后,IE可以在任何元素(严格模式)上使用:hover这个CSS伪选择器。然而,如果大量的元素使用了:hover那么会降低反应速度。此问题在IE8中更显著。

 

    For example, if you create a table with 500–1000 rows and 5 columns and use tr:hover to change the background color and highlight the row the user is on, the performance degrades as the user moves over the table. The highlight is slow to apply, and the CPU usage increases to 80%–90%. So avoid this effect when you work with a large number of elements, such as big tables or long item lists.

    例如,如果你创建了一个由500-1000行5列构成的表,并使用tr:hover改变背景颜色,高亮显示鼠标光标所在的行,当鼠标光标在表上移动时,性能会降低。使用高亮是个慢速过程,CPU使用率会提高到80%-90%。所以当元素数量很多时避免使用这种效果,诸如很大的表或很长的列表。

 

Event Delegation  事件托管

 

    When there are a large number of elements on a page and each of them has one or more event handlers attached (such as onclick), this may affect performance. Attaching every handler comes at a price—either in the form of heavier pages (more markup or JavaScript code) or in the form of runtime execution time. The more DOM nodes you need to touch and modify, the slower your application, especially because the event attaching phase usually happens at the onload (or DOMContentReady) event, which is a busy time for every interaction-rich web page. Attaching events takes processing time, and, in addition, the browser needs to keep track of each handler, which takes up memory. And at the end of it, a great number of these event handlers might never be needed(because the user clicked one button or link, not all 100 of them, for example), so a lot of the work might not be necessary.

    当页面中存在大量元素,而且每个元素有一个或多个事件句柄与之挂接(例如onclick)时,可能会影响性能。连接每个句柄都是有代价的,无论其形式是加重了页面负担(更多的页面标记和JavaScript代码)还是表现在运行期的运行时间上。你需要访问和修改更多的DOM节点,程序就会更慢,特别是因为事件挂接过程都发生在onload(或DOMContentReady)事件中,对任何一个富交互网页来说那都是一个繁忙的时间段。挂接事件占用了处理时间,另外,浏览器需要保存每个句柄的记录,占用更多内存。当这些工作结束时,这些事件句柄中的相当一部分根本不需要(因为并不是100%的按钮或者链接都会被用户点到),所以很多工作都是不必要的。

 

    A simple and elegant technique for handling DOM events is event delegation. It's based on the fact that events bubble up and can be handled by a parent element. With event delegation, you attach only one handler on a wrapper element to handle all events that happen to the children descendant of that parent wrapper.

    一个简单而优雅的处理DOM事件的技术是事件托管。它基于这样一个事实:事件逐层冒泡总能被父元素捕获。采用事件托管技术之后,你只需要在一个包装元素上挂接一个句柄,用于处理子元素发生的所有事件。

 

    According to the DOM standard, each event has three phases:

    根据DOM标准,每个事件有三个阶段:

 

• Capturing

  捕获
• At target

  到达目标
• Bubbling

  冒泡

 

    Capturing is not supported by IE, but bubbling is good enough for the purposes of delegation. Consider a page with the structure shown in Figure 3-8.

    IE不支持捕获,但实现托管技术使用冒泡就足够了。考虑图3-8所示的页面结构。

 

[翻译]High <wbr>Performance <wbr>JavaScript(011)

Figure 3-8. An example DOM tree

图3-8  一个DOM树的例子

 

    When the user clicks the "menu #1" link, the click event is first received by the <a> element. Then it bubbles up the DOM tree and is received by the <li> element, then the <ul>, then the <div>, and so on, all the way to the top of the document and even the window. This allows you to attach only one event handler to a parent element and receive notifications for all events that happen to the children.

    当用户点击了“menu #1”链接,点击事件首先被<a>元素收到。然后它沿着DOM树冒泡,被<li>元素收到,然后是<ul>,接着是<div>,等等,一直到达文档的顶层,甚至window。这使得你可以只在父元素上挂接一个事件句柄,来接收所有子元素产生的事件通知。

 

    Suppose that you want to provide a progressively enhanced Ajax experience for the document shown in the figure. If the user has JavaScript turned off, then the links in the menu work normally and reload the page. But if JavaScript is on and the user agent is capable enough, you want to intercept all clicks, prevent the default behavior (which is to follow the link), send an Ajax request to get the content, and update a portion of the page without a refresh. To do this using event delegation, you can attach a click listener to the UL "menu" element that wraps all links and inspect all clicks to see whether they come from a link.

    假设你要为图中所显示的文档提供一个逐步增强的Ajax体验。如果用户关闭了JavaScript,菜单中的链接仍然可以正常地重载页面。但是如果JavaScript打开而且用户代理有足够能力,你希望截获所有点击,阻止默认行为(转入链接),发送一个Ajax请求获取内容,然后不刷新页面就能够更新部分页面。使用事件托管实现此功能,你可以在UL"menu"单元挂接一个点击监听器,它封装所有链接并监听所有click事件,看看他们是否发自一个链接。

 

document.getElementByIdx('menu').onclick = function(e) {
  // x-browser target
  e = e || window.event;
  var target = e.target || e.srcElement;
  var pageid, hrefparts;
  // only interesed in hrefs
  // exit the function on non-link clicks
  if (target.nodeName !== 'A') {
    return;
  }
  // figure out page ID from the link
  hrefparts = target.href.split('/');
  pageid = hrefparts[hrefparts.length - 1];
  pageid = pageid.replace('.html', '');
  // update the page
  ajaxRequest('xhr.php?page=' + id, updatePageContents);
  // x-browser prevent default action and cancel bubbling
  if (typeof e.preventDefault === 'function') {
    e.preventDefault();
    e.stopPropagation();
  } else {
    e.returnValue = false;
    e.cancelBubble = true;
  }
};

    As you can see, the event delegation technique is not complicated; you only need to inspect events to see whether they come from elements you're interested in. There's a little bit of verbose cross-browser code, but if you move this part to a reusable library, the code becomes pretty clean. The cross-browser parts are:

    正如你所看到的那样,事件托管技术并不复杂;你只需要监听事件,看看他们是不是从你感兴趣的元素中发出的。这里有一些冗余的跨浏览器代码,如果你将它们移入一个可重用的库中,代码就变得相当干净。跨浏览器部分包括:

 

• Access to the event object and identifying the source (target) of the event

  访问事件对象,并判断事件源(目标)


• Cancel the bubbling up the document tree (optional)

  结束文档树上的冒泡(可选)


• Prevent the default action (optional, but needed in this case because the task was to trap the links and not follow them)

  阻止默认动作(可选,但此例中是必须的,因为任务是捕获链接而不转入这些链接)

 

Summary  总结

 

    DOM access and manipulation are an important part of modern web applications. But every time you cross the bridge from ECMAScript to DOM-land, it comes at a cost. To reduce the performance costs related to DOM scripting, keep the following in mind:

    DOM访问和操作是现代网页应用中很重要的一部分。但每次你通过桥梁从ECMAScript岛到达DOM岛时,都会被收取“过桥费”。为减少DOM编程中的性能损失,请牢记以下几点:

 

• Minimize DOM access, and try to work as much as possible in JavaScript.

  最小化DOM访问,在JavaScript端做尽可能多的事情。


• Use local variables to store DOM references you'll access repeatedly.

  在反复访问的地方使用局部变量存放DOM引用.


• Be careful when dealing with HTML collections because they represent the live, underlying document. Cache the collection length into a variable and use it when iterating, and make a copy of the collection into an array for heavy work on collections.

  小心地处理HTML集合,因为他们表现出“存在性”,总是对底层文档重新查询。将集合的length属性缓存到一个变量中,在迭代中使用这个变量。如果经常操作这个集合,可以将集合拷贝到数组中。


• Use faster APIs when available, such as querySelectorAll() and firstElementChild.

  如果可能的话,使用速度更快的API,诸如querySelectorAll()和firstElementChild。


• Be mindful of repaints and reflows; batch style changes, manipulate the DOM tree "offline," and cache and minimize access to layout information.

  注意重绘和重排版;批量修改风格,离线操作DOM树,缓存并减少对布局信息的访问。


• Position absolutely during animations, and use drag and drop proxies.

  动画中使用绝对坐标,使用拖放代理。


• Use event delegation to minimize the number of event handlers.

  使用事件托管技术最小化事件句柄数量。

0

阅读 评论 收藏 转载 喜欢 打印举报/Report
  • 评论加载中,请稍候...
发评论

    发评论

    以上网友发言只代表其个人观点,不代表新浪网的观点或立场。

      

    新浪BLOG意见反馈留言板 电话:4000520066 提示音后按1键(按当地市话标准计费) 欢迎批评指正

    新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 会员注册 | 产品答疑

    新浪公司 版权所有