2012年6月23日

Ext JS 4.1 Performance 中文摘要

原文連結 http://www.sencha.com/blog/ext-js-4-1-performance
Ext JS 4.1 Performance

這篇文章主要討論幾項影響 Ext JS 應用程式效能的因子。

網路延遲除了會嚴重影響網頁載入初始化的時間,同時也會影響 Data Store 讀取時間。

  • CSS processing.
  • JavaScript execution.
  • DOM manipulation.
網路延遲(Network Latency)

為了減少應用程式啟動時間,你需要明白瀏覽器對任何域名(domain)的同時連線數量有其限制。


意思就是如果對同一個域名存取多個檔案,在瀏覽器的連線數量額度限制下,下載檔案的連線會在佇列中等待,直到有空閒的連線才被處理。較新的瀏覽器允許較多的連線數量,但是針對較舊較慢的瀏覽器仍必須最佳化。

解決方法是使用 Sencha SDK 工具(可以從 ExtJS 壓縮檔取得),將應用程式的所有 JavaScript 最佳化成單一檔案。

See Getting Started with Ext JS 4.0 for details.

這個工具會分析 ExtJS 應用程式的 JavaScript 程式碼,用到 requires 及 uses 語法的類別定義,就會依照正確的順序將程式碼加到單一的 .js 檔案。

See the Ext JS 4.1 Docs for details about the Sencha class system.

其他減少網路延遲的方法,是開啟 Web Server 的 GZIP 壓縮功能,讓 JavaScript 及 CSS 檔案可以先經過壓縮再傳送給瀏覽器。

選擇器(CSS Processing)

CSS 選擇器的檢索是從右到左,透過 DOM 的父節點指標。

意思是說,如果這樣寫選擇器:

.HeaderContainer .nav span

這段選擇器會配對文件中的每一個 span 標籤,並從父節點尋找上層標籤是否有指定的 class 名稱。

相較之下直接選擇指定 class 名稱的標籤會更有效率(span.nav-items)。

JavaScript Execution

撰寫效能最佳化的 JavaScript 程式碼必須牢記幾點:

  • 避免使用老舊或差勁的 JavaScript 引擎(Avoid older or badly written JavaScript engines.)
  • 頻繁地重複最佳化程式碼(Optimize code which is repeated frequently.)
  • 對於影響畫面繪製的程式碼最佳會(Optimize code which is executed at render or layout time.)
  • 最好的作法就是避免在畫面繪製的初始化階段執行額外的程式碼(Better still, try not to execute any extra code at initial render or layout time.)
  • 在進入迴圈之前就盡可能將不會隨迴圈變動的資料先計算好(Move invariant expressions outside of loops)
  • 盡量使用 for 迴圈取代 Ext.Array.each(Use for (...) rather than Ext.Array.each)
  • 如果函式會依照條件判斷執行任務並且頻繁地被呼叫,在呼叫前就先將判斷處理好,並且只有在必要時才呼叫(If a function performs its task conditionally and is frequently called, check the condition outside the call, and only call it if necessary. (See calls to fireEvent in Ext JS codebase))
  • Setup and teardown of a call frame (apparatus needed to make a function call) is slow on bad JavaScript engines

Code optimization example

不好的寫法使用 Ext.Array.each:

function badTotalFn(menuItem) {
   var r = store.getRange(),
       total = 0;

   Ext.Array.each(r, function(rec) {
       total += rec.get(menuItem.up('dataIndex').dataIndex);
   });
}

這個寫法有兩個不好的地方,首先 Ext.each 每次都要呼叫被傳入的匿名函式,匿名函式的設置會影響效能。再來是 menuItem.up('dataIndex') 的運算結果並不會改變,所以應該移到函式執行前。

最佳化之後的程式碼如下:

function goodTotalFn(menuItem) {
    var r = store.getRange(),
        field = menuItem.up('dataIndex').dataIndex;
        total = 0;

    for (var j = 0, l = r.length; j < l; j++) {
        total += r[j].get(field);
    }
 }

雖然這兩種寫法看起來差異不大,結果也相同,但效能卻明顯差很大。

下面的表格是這兩種寫法在不同瀏覽器的測試結果(執行 10000 ):


BrowserBadGood
Chrome1700ms10ms
IE918000ms500ms
IE6Gave up532ms


譯註:雖然該死萬惡的 IE 不管怎樣都跑很慢,但不同寫法的差異還是相當顯著。

Use Page Analyzer to measure performance

Page Analyzer 是 SDK 的範例,位置在  example/page-analyzer ,它可以用來分析任何物件方法的效能。

如果你使用 Chrome 瀏覽器,請在執行時加上 --enable-benchmarking 參數以切換時間的精確度到 microseconds。

合併多個畫面佈局動作(Coalesce multiple layouts)

Ext JS 4 在內容改變或尺寸變動時,會自動將畫面佈局重整。意思就是按鈕的文字有所改變時,它所在的 Toolbar 也會被影響,因為按鈕的寬高可能改變;所以,再上一層的 Panel 也會被影響。

對於這種情況,如果有多處內容都要修改時,可以合併佈局動作(避免每改一筆,整個畫面就要重整一次):

{
    Ext.suspendLayouts();
    // batch of updates
    Ext.resumeLayouts(true);
}

傳入 true 參數代表重新允許畫面佈置,此時就會從佇列得到需要重整的畫面佈局。

Reduce DOM burden

減少太多層的容器/元件可以避免重複的佈局動作及 DOM reflow 次數,這些代價可能非常高。

最基本的原則就是使用最簡化的容器(Container)或佈局(Layout)來完成必要的工作。

過多層次最常見的範例,就是在 TabPanel 中放置 Grid 時,新手可能會這樣做:

{
    xtype: &lsquo;tabpanel&rsquo;,
    items: [{
        title: &lsquo;Results&rsquo;,
        items: {
            xtype: &lsquo;grid&rsquo;
            ...
        }
    }]
}

Grid 本身就是繼承自 Panel,所以可以直接放在 items,上面的寫法會額外建立一個 Panel 並且只放進 Grid,這會產生多餘的一層。

正確的方法應該是:

{
    xtype: &lsquo;tabpanel&rsquo;,
    items: [{
        title: &lsquo;Results&rsquo;,
        xtype: &lsquo;grid&rsquo;,
        ...
    }]
}

為什麼這個原則很重要呢?

盡可能保持元件樹(或者說 DOM tree )盡可能輕量,因為在 Ext 4 中,愈多擁有子項目的容器元件,就會執行愈多的它們自己的佈局管理器(layout managers),舉例來說,Panel 的標題是一級容器,它可以被設置其他子項目例如標題文字或工具。

這聽起來有點太過 over,但這樣才能允許 UI 設計師足夠的彈性。

Ext JS 4 的元件也使用 Component Layout Managers 來管理大小及它內部的 DOM 結構,不像 Ext JS 3.x 都在 onResize 處理。

Visualize the component tree

在設計 UI 時可以試著將它表示為樹狀結構,例如一個 Viewport 可以這樣表示:


為了要繪製(render)元件樹需要兩個步驟。

第一個步驟,每個元件的 beforeRender 事件被呼叫,然後 getRenderTree 產生一個 DomHelper 的設定物件,它會被轉換成 HTML 標籤並且被增加到繪製緩衝區(render buffer)。

接著是第二個步驟,可以代表整個元件樹的 HTML 被一次加到網頁文件(document)中,這樣做的目的是為了減少在建立應用程式元件時處理 DOM 的次數。

然後每個元件的 onRender 被呼叫,此時每個元件已經跟 DOM 節點連接起來。接著是 afterRender 最後被呼叫來完成畫面繪製的程序。

在這些動作都完成後,畫面佈局的初始化才算全部完成。

這就是為什麼建立輕量化的 UI 設計很重要!

考慮以下的 Panel 規格定義:

Ext.create('Ext.panel.Panel', {
    width: 400, height: 200,
    icon: '../shared/icons/fam/book.png',
    title: 'Test',
    tools: [{
        type: 'gear'
    }, {
        type: 'pin'
    }],
    renderTo: document.body
});

雖然看起來很簡單,但其實它已經會產生一個相當複雜的 UI 結構。

Avoid shrinkwrapping (auto sizing based on content) where possible.

shrinkwrapping 是元件依據內容自動調整寬高大小的機制,如果可以的話,將它停用就可以提昇一些效能。



Avoid size constraints (minHeight, maxHeight, minWidth, maxWidth) where possible.

如果可以的話,盡可能取消最小及最大寬度或高度的限制。

Avoid postprocessing DOM of Component in afterRender

避免在 afterRender 事件中改變元件的 HTML DOM,因為這會帶來對效能造成影響的 DOM reflow 及 repaint。取而代之的方式,是利用 hook 的方式在元件的 HTML 產生前就先做好修改。


如果修改 DOM 結構是必要的,那就使用 getRenderTree 來取代直接修改。

Grid performance

資料表的大小會影響效能,特別是欄位的數量,所以盡可能減少欄位的數量。

如果資料集非常大,而且介面允許用 PagingToolbar(分頁工具列),就可以使用有緩衝區的設計(infinite grid)。

實作的方式只要加上設定:
buffered: true,
pageSize: 50, // Whatever works best given your network/DB latency
autoLoad: true

載入和維護資料的方法則不需要改變。

How it works

緩衝的設定會讓 Grid 依照可視區域的大小來決定顯示的筆數,移動垂直捲軸會觸發載入其他資料的事件,緩衝區會預讀資料讓捲動表現更平順,更詳細的設定如下:

{
    xtype: 'gridpanel',
    verticalScroller: {
        numFromEdge: 5,
        trailingBufferZone: 10,
        leadingBufferZone: 20
    }
}

將整個資料集預置到客戶端(Pull the whole dataset client side!)

例如將 leadingBufferZone 設定為 50,000 並將 purgePageCount 設定為 0,就會先將資料從伺服器下載到 Store,如此一來 pipeline 就會先滿載資料,而減少不斷向伺服器要求資料需要的等待時間。

Written by Nige "Animal" White
Nigel brings more than 20 years experience to his role as a software architect at Sencha. He has been working with rich Internet applications, and dynamic browser updating techniques since before the term "Ajax" was coined. Since the germination of Ext JS he has contributed code, documentation, and design input.

原文連結 http://www.sencha.com/blog/ext-js-4-1-performance

沒有留言:

張貼留言

lyhcode by lyhcode
歡迎轉載,請務必註明出處!