html頁面顯示過程
- 解析HTML,並生成一棵DOM tree
- 解析各種樣式並結合DOM tree生成一棵Render tree
- 對Render tree的各個節點計算布局信息,比如box的位置與尺寸
- 根據Render tree並利用瀏覽器的UI層進行繪制流程。
其中,第三步,對render tree的各個結點計算布局信息為時間占用較大的一部分,而在這一步中,包含了layout,layout操作,是對render tree中對象的大小、尺寸進行計算,在默認情況下,瀏覽器的layout為lazy模式,也就是說,並非每次我們對DOM進行修改時都會layout,而是將這些修改存儲在一個隊列中,在一定的情況下統一提交隊列,進而實現layout操作。
1.批量讀寫
當我們需要獲取某一屬性,這一屬性需要計算才能得到,並且隊列中存在尚未提交的DOM修改操作,則此時,DOM修改操作的隊列將會被提交。
為了提高效率,減少更新render tree的次數,可以先統一讀取屬性,然后統一修改DOM,這樣,就可以減少更新render tree的次數。
2.虛擬結點
當我們需要對DOM做出大量修改時,可以先創建一個虛擬結點,將所有修改附加在該節點,最后將該虛擬節點一次性提交給在render tree上存在的結點,則相當於只提交了一次修改操作。
3.查找元素的優化
因為ID是唯一的,也有原始的方法,因此使用ID查找元素是最快的,其次的是根據類和類型查找元素,通過屬性查找元素是最慢的,因此應該盡可能的通過ID或者類來查找元素,避免通過類來查找元素。
4.改變DOM,包括添加,修改,刪除DOM
改變DOM就會引起瀏覽器渲染,而渲染是相當慢的,因此應該避免不必要的渲染。
5.改變DOM的樣式類等
改變DOM元素的樣式,類也會導致瀏覽器渲染,因此也應該減少不必要的操作。
6.減少iframe
iframe需要消耗大量的時間,並阻塞下載,應該少用。
7.樣式放在head中,腳本放在關閉標簽</body>之前
樣式放在head中,可以加快渲染,腳本放在關閉標簽</body>之前可以加快下載速度,不免阻塞下載。
1. 批量增加Dom
盡量使用修改innerHTML的方式而不是用appendChild的方式; 因為使用innerHTML開銷更小,速度更快,同時也更加內存安全.
有一點需要注意的是,用innerHTML方式添加時,一定不要在循環中使用 innerHTML += 的方式添加,這樣反而會使速度減慢; 而是應該中間用array緩存起來,循環結束后調用 xx.innerHTML = array.join(‘’);的方式,或者至少保存到string中再插到innerHTML中.
針對用戶列表一塊采用這種方式優化后,加載速度提升一倍.
2. 單個增加Dom
這里是指要將新節點加載到一個內容不斷變化的節點的情形,對於內容穩定的節點來說,隨便怎么加都沒有問題. 但是對於有動態內容的節點來說,為其添加子節點盡量使用 dom append的方式.
這是因為,dom append不會影響到其他的節點;而如果修改innerHTML屬性的話,該父節點的所有子節點都會從dom樹中剝離,再根據新的innerHTML值來重繪子節點dom樹;所有注冊到原來子節點的事件也會失效.
綜上,如果在一個有動態內容的節點上 出現了 innerHTML += 的代碼,就該考慮是否有問題了.
3. 創建Dom節點
用 createElement方式創建一個dom節點,有一個很重要的細節: 在執行完createElement代碼之后,應該馬上append到dom樹中; 否則,如果在將這個孤立節點加載到dom樹之前所做的賦值它的屬性和innerHTML的操作都會引發該dom片段內存無法回收的問題. 這個不起眼細節,一旦遇到大量dom增刪操作,就會引發內存的災難.
4. 刪除Dom節點
刪除dom節點之前,一定要刪除注冊在該節點上的事件,不管是用observe方式還是用attachEvent方式注冊的事件,否則將會產生無法回收的內存.
另,在removeChild和innerHTML=’’二者之間,盡量選擇后者. 因為在sIEve(內存泄露監測工具)中監測的結果是用removeChild無法有效地釋放dom節點.
5. 創建事件監聽
現有的js庫都采用observe方式來創建事件監聽,其實現上隔離了dom對象和事件處理函數之間的循環引用,所以應該盡量采用這種方式來創建事件監聽.
6. 監聽動態元素
Dom 事件默認是向上冒泡的,發生在子節點中的事件,可以由父節點來處理. Event的 target/srcElement 仍是產生事件的最深層子節點. 這樣,對於內容動態增加並且子節點都需要相同的事件處理函數的情況,可以把事件注冊上提到父節點上,這樣就不需要為每個子節點注冊事件監聽了.
同時,這樣做也避免了產生無法回收的內存.即使是用Prototype的observe方式注冊事件並在刪除節點前調用stopObserving,也會產生出少量無法回收的內存,所以應該盡量少的為dom節點注冊事件監聽.
所以,當代碼中出現在循環里注冊事件時,也是我們該考慮事件上提機制的時候了.
