[翻譯]High Performance JavaScript(011)

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所示。

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.getElementById('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.getElementById('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.getElementById('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('a');
    a.href = data[i].url;
    a.appendChild(document.createTextNode(data[i].name));
    li = document.createElement('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.getElementById('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.getElementById('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.getElementById('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.getElementById('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所示的頁面結構。

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.getElementById('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.

  使用事件託管技術最小化事件句柄數量。

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