第三章 DOM Scripting dom 文檔對象模型 類似的還有 bom 瀏覽器對象模型 。 要使用一個 對象首先要知道該對象存在的位置,比如 document 對象 它並不是被定義在 ECMAScript 中而是 dom 中。所以 本來你每次訪問 document就很慢。 引用: 文檔對象模型 ...
第三章 DOM Scripting
- 最小化 DOM 訪問,在 JavaScript 端做儘可能多的事情。
- 在反覆訪問的地方使用局部變數存放 DOM 引用.
- 小心地處理 HTML 集合,因為他們表現出“存在性”,總是對底層文檔重新查詢。將集合的 length 屬性緩
- 存到一個變數中,在迭代中使用這個變數。如果經常操作這個集合,可以將集合拷貝到數組中。
- 如果可能的話,使用速度更快的 API,諸如 querySelectorAll()和 firstElementChild。
- 註意重繪和重排版;批量修改風格,離線操作 DOM 樹,緩存並減少對佈局信息的訪問。
- 動畫中使用絕對坐標,使用拖放代理。
- 使用事件托管技術最小化事件句柄數量。
- 上面八條建議都是抄的。
dom 文檔對象模型 類似的還有 bom 瀏覽器對象模型 。
要使用一個 對象首先要知道該對象存在的位置,比如 document 對象 它並不是被定義在 ECMAScript 中而是 dom 中。所以 本來你每次訪問 document就很慢。
引用:
文檔對象模型(DOM)是一個獨立於語言的,使用 XML和 HTML 文檔操作的應用程式介面(API)。
在瀏覽器中,主要與 HTML 文檔打交道,在網頁應用中檢索 XML 文檔也很常見。DOM APIs 主要用於訪
問這些文檔中的數據。
儘管 DOM 是與語言無關的 API,在瀏覽器中的介面卻是以 JavaScript 實現的。客戶端大多數腳本程式
與文檔打交道,DOM 就成為 JavaScript 代碼日常行為中重要的組成部分。
瀏覽器通常要求 DOM 實現和 JavaScript 實現保持相互獨立。 例如, 在 Internet Explorer中, 被稱為 JScript
的 JavaScript實現位於庫文件 jscript.dll中,而 DOM 實現位於另一個庫 mshtml.dll(內部代號 Trident)。
兩個獨立的部分以功能介面連接就會帶來性能損耗,這就是為什麼會有上面8條建議。
註意:
1、html集合的存在性
eg:document.getElementsByName() 返回一個類數組對象,這個對象就是 HTML集合。
eg:
// an accidentally infinite loop var alldivs = document.getElementsByTagName_r('div'); for (var i = 0; i < alldivs.length; i++) { document.body.appendChild(document.createElement('div')) }
說明:這段代碼看上去只是簡單地倍增了頁面中 div元素的數量。它遍歷現有 div,每次創建一個新的 div並附
加到 body上面。但實際上這是個死迴圈,因為迴圈終止條件 alldivs.length 在每次迭代中都會增加,它反
映出底層文檔的當前狀態。
其實這裡還有第二個問題,如果迴圈里沒有做任何操作,這是一個正常的迴圈,但它還是存在性能問題,因為目標是一個HTML集合。
簡單說就是每次迴圈 都會訪問 length 屬性,而length屬性的值是通過查詢文檔的操作得到的。每獲取一次length都會重新查詢一次文檔,以保證獲取到的數據是最新的,上面有一個現成的例證。更要命的是查詢文檔這個操作是天生就慢的。原因就在上面,有一段話可以加深印象:
一個很形象的比喻是把 DOM 看成一個島嶼,把 JavaScript(ECMAScript)看成另一個島嶼,兩者之間以一座收費橋連接(參見 John Hrvatin,微軟,MIX09,http://videos.visitmix.com/MIX09/T53F)。每次 ECMAScript 需要訪問 DOM 時,你需要過橋,交一次“過橋費”。
所以,在迴圈的時候 把 length緩存到一個變數里最好。
如果把 alldivs 複製到一個數組內 例如 :
function toArray(coll) { for (var i = 0, a = [], len = coll.length; i < len; i++) { a[i] = coll[i]; } return a; }
可以更優的解決問題,如果你需要在迴圈中多次操作item的話(建議4)。 同時 len = coll.length 這樣寫是非常值得學習的,但是在C#中就不曉得有沒有益了。
2、使用速度更快的api,這是一種更優的替代解決方案 eg: var elements = document.getElementById('menu').getElementsByTagName_r('a'); elements =
toArray(elements); 和 var elements = document.querySelectorAll('#menu a');
建議5:這兩個函數都是 DOM 節點的屬性,所以你可以使用 document.querySelector('.myclass')來查詢整個文檔中的節點,或者使用 elref.querySelector('.myclass')在子樹中進行查詢,其中 elref是一個 DOM 元素的引用。
3、 重繪和重拍版引用原文的解釋:
當瀏覽器下載完所有頁面 HTML標記,JavaScript,CSS,圖片之後,它解析文件並創建兩個內部數據
結構: A DOM tree 表示頁面結構 A render tree 表示 DOM 節點如何顯示 。
渲染樹中為每個需要顯示的 DOM 樹節點存放至少一個節點(隱藏 DOM 元素在渲染樹中沒有對應節
點)。渲染樹上的節點稱為“框”或者“盒”,符合 CSS 模型的定義,將頁面元素看作一個具有填充、邊距、
邊框和位置的盒。一旦 DOM 樹和渲染樹構造完畢,瀏覽器就可以顯示(繪製)頁面上的元素了。
當 DOM 改變影響到元素的幾何屬性(寬和高)——例如改變了邊框寬度或在段落中添加文字,將發生
一系列後續動作——瀏覽器需要重新計算元素的幾何屬性,而且其他元素的幾何屬性和位置也會因此改變
受到影響。瀏覽器使渲染樹上受到影響的部分失效,然後重構渲染樹。這個過程被稱作重排版。重排版完
成時,瀏覽器在一個重繪進程中重新繪製屏幕上受影響的部分。
不是所有的 DOM 改變都會影響幾何屬性。例如,改變一個元素的背景顏色不會影響它的寬度或高度。
在這種情況下,只需要重繪(不需要重排版),因為元素的佈局沒有改變。
例子:(有可能根本不會出現,只為分析問題使用)
// 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(docume } // 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; PK: bodystyle.color = 'red'; bodystyle.color = 'white'; bodystyle.color = 'green'; tmp = computed.backgroundColor; tmp = computed.backgroundImage; tmp = computed.backgroundAttachment; //winner
bodystyle.color 雖然和 computed.backgroundColor等三個屬性無關,但是 瀏覽器仍要刷新渲染隊列並重排版,註意computed被查詢了。(如果把computed緩存到變數里就不會有這種情況)
這也是為什麼 後者PK勝出了。
還有一種情況更加具有畫面感,
var el = document.getElementById('mydiv'); el.style.borderLeft = '1px'; el.style.borderRight = '2px'; el.style.padding = '5px';
這段代碼使瀏覽器排版了三次。只不過速度比較快感官上只改變了一次,但是可以想象到繪製三次的畫面。
(註意:現在大多數瀏覽器都優化了這種情況,只排版一次。)
如同下麵代碼一樣:
var el = document.getElementById('mydiv'); el.style.cssText = 'border-left: 1px; border-right: 2px; padding: 5px;'; el.style.cssText += '; border-left: 1px;'; //or el.className = 'active'; //or
離線dom 很好理解,就是在 操作dom的時候不產生 重繪、重排,操作完成後一次性 ‘提交’。
離線dom的三種方式: 1、隱藏 and 操作 and 顯示 eg:
var ul = document.getElementById('mylist'); ul.style.display = 'none'; appendDataToElement(ul, data); ul.style.display = 'block';
2、創建並更新一個文檔片斷,然後附加或替換,eg:
var fragment = document.createDocumentFragment(); appendDataToElement(fragment, data); document.getElementById('mylist').appendChild(fragment);
3、製作副本,替換原節點eg:
var old = document.getElementById('mylist'); var clone = old.cloneNode(true); appendDataToElement(clone, data); old.parentNode.replaceChild(clone, old);
緩存並減少對佈局信息的訪問就是類似 於提取局部變數,只不過佈局信息這類數據的訪問影響比較大。
4、動畫
- 使用絕對坐標定位頁面動畫的元素,使它位於頁面佈局流之外。
- 比如一個擴大和縮小的動畫,當擴大使其覆蓋原有佈局,重繪這一部分而不會重排、重繪一大部分頁面。
- 動畫結束時,重新定位。 其他元素是一起被覆位的。
5、事件托管技術最小化事件句柄數量
這裡說自己的理解有可能是錯誤的,暫時先這樣理解了:
首先 每個事件都有三個階段:
• Capturing
捕獲
• At target
到達目標
• Bubbling
冒泡
在onload(或DOMContentReady) 事件里 會讓 很多元素和很多事件句柄 掛接/附加 (attached) ,而 有一些掛接是不必的。比如說一些根本不會被點到的按鈕。
引用:
一個簡單而優雅的處理 DOM 事件的技術是事件托管。它基於這樣一個事實:事件逐層冒泡總能被父元
素捕獲。採用事件托管技術之後,你只需要在一個包裝元素上掛接一個句柄,用於處理子元素髮生的所有
事件。