組內(nèi)同時總結(jié)的關于javascript性能優(yōu)化注意些節(jié)。記錄一下。
1. 批量增加 Dom
盡量使用修改 innerHTML 的方式而不是用 appendChild 的方式 ; 因為使用 innerHTML 開銷更小 , 速度更快 , 同時也更加內(nèi)存安全 .
有一點需要注意的是 , 用 innerHTML 方式添加時 , 一定不要在循環(huán)中使用 innerHTML += 的方式添加 , 這樣反而會使速度減慢 ; 而是應該中間用 array 緩存起來 , 循環(huán)結(jié)束后調(diào)用 xx.innerHTML = array.join(‘’); 的方式 , 或者至少保存到 string 中再插到 innerHTML 中 .
針對用戶列表一塊采用這種方式優(yōu)化后 , 加載速度提升一倍 .
2. 單個增加 Dom
這里是指要將新節(jié)點加載到一個內(nèi)容不斷變化的節(jié)點的情形 , 對于內(nèi)容穩(wěn)定的節(jié)點來說 , 隨便怎么加都沒有問題 . 但是對于有動態(tài)內(nèi)容的節(jié)點來說 , 為其添加子節(jié)點盡量使用 dom append 的方式 .
這是因為 ,dom append 不會影響到其他的節(jié)點 ; 而如果修改 innerHTML 屬性的話 , 該父節(jié)點的所有子節(jié)點都會從 dom 樹中剝離 , 再根據(jù)新的 innerHTML 值來重繪子節(jié)點 dom 樹 ; 所有注冊到原來子節(jié)點的事件也會失效 .
綜上 , 如果在一個有動態(tài)內(nèi)容的節(jié)點上 出現(xiàn)了 innerHTML += 的代碼 , 就該考慮是否有問題了 .
3. 創(chuàng)建 Dom 節(jié)點
用 createElement 方式創(chuàng)建一個 dom 節(jié)點 , 有一個很重要的細節(jié) : 在執(zhí)行完 createElement 代碼之后 , 應該馬上 append 到 dom 樹中 ; 否則 , 如果在將這個孤立節(jié)點加載到 dom 樹之前所做的賦值它的屬性和 innerHTML 的操作都會引發(fā)該 dom 片段內(nèi)存無法回收的問題 . 這個不起眼細節(jié) , 一旦遇到大量 dom 增刪操作 , 就會引發(fā)內(nèi)存的災難 .
4. 刪除 Dom 節(jié)點
刪除 dom 節(jié)點之前 , 一定要刪除注冊在該節(jié)點上的事件 , 不管是用 observe 方式還是用 attachEvent 方式注冊的事件 , 否則將會產(chǎn)生無法回收的內(nèi)存 .
另 , 在 removeChild 和 innerHTML=’’ 二者之間 , 盡量選擇后者 . 因為在 sIEve( 內(nèi)存泄露監(jiān)測工具 ) 中監(jiān)測的結(jié)果是用 removeChild 無法有效地釋放 dom 節(jié)點 .
5. 創(chuàng)建事件監(jiān)聽
現(xiàn)有的 js 庫都采用 observe 方式來創(chuàng)建事件監(jiān)聽 , 其實現(xiàn)上隔離了 dom 對象和事件處理函數(shù)之間的循環(huán)引用 , 所以應該盡量采用這種方式來創(chuàng)建事件監(jiān)聽 .
6. 監(jiān)聽動態(tài)元素
Dom 事件默認是向上冒泡的 , 發(fā)生在子節(jié)點中的事件 , 可以由父節(jié)點來處理 . Event 的 target/srcElement 仍是產(chǎn)生事件的最深層子節(jié)點 . 這樣 , 對于內(nèi)容動態(tài)增加并且子節(jié)點都需要相同的事件處理函數(shù)的情況 , 可以把事件注冊上提到父節(jié)點上 , 這樣就不需要為每個子節(jié)點注冊事件監(jiān)聽了 .
同時 , 這樣做也避免了產(chǎn)生無法回收的內(nèi)存 . 即使是用 Prototype 的 observe 方式注冊事件并在刪除節(jié)點前調(diào)用 stopObserving, 也會產(chǎn)生出少量無法回收的內(nèi)存 , 所以應該盡量少的為 dom 節(jié)點注冊事件監(jiān)聽 .
所以 , 當代碼中出現(xiàn)在循環(huán)里注冊事件時 , 也是我們該考慮事件上提機制的時候了 .
7. HTML 提純
HTML 提純體現(xiàn)的是一種各負其責的思想 . HTML 只用來顯示 , 盡量不出現(xiàn)和顯示無關的屬性 . 比如 onclick 事件 , 比如自定義的對象屬性 .
事件可以用前面的方法避免 , 對象屬性指的是這樣的一種情景 : 通常情況下 , 動態(tài)增加的內(nèi)容都是有個對象和它對應 , 比如聊天室的用戶列表 , 每個顯示用戶的 dom 節(jié)點都有一個 user 對象和它對應 , 這樣在 html 中 , 應該僅保留一個 id 屬性和 user 對象對應 , 而其他的信息 , 則應通過 user 對象去獲取 .
基于 PrototypeJS ,寫了一個 Dom 生成器,以提供簡單高效的 HTML 操作接口的語法和語義優(yōu)化 :
1. 使用類似 JSONML 的格式 (HTML in JSON) 描述 DOM 結(jié)構(gòu),以補充 HTML 轉(zhuǎn)義字符串的表達形式,規(guī)范格式中可支持以下 HTML 語義:
l 標簽名
l 屬性 ( 標識符 , 類名 , 事件名 , 內(nèi)聯(lián)樣式等 )
注:事件名上注冊的偵聽函數(shù)將自動通過 Event.observe 方式添加
l 嵌套標簽
格式規(guī)范可簡單描述為:
{
tag:string, // 元素的標記名,如果沒有,默認為 div
children|cn: string|Array|json, // 子結(jié)點對應的 json 數(shù)組或字節(jié)點的 html 或單個 json
html:string, // 對應的 html ,如果有 cn 或 children 屬性就忽略
style:function|string|json, // 元素的樣式,可以是函數(shù),字符串, json 對象
cls:string, // 元素的 class 屬性的值
x:y // x 表示其他名字, y 表示變量值、非空字符串
onXXX: function // 以 on 為首的屬性是事件偵聽器
}
一個具體的例子為:
var list = DomBuilder.append( 'my-div' , {
tag : 'ul' ,
cls : 'my-list' ,
children : [
{
tag : 'li' ,
id : 'item1' ,
html : 'List Item 1' ,
onclick : function () {
alert( 'List Item1 Clicked' )
}
},
{
tag : 'li' ,
id : 'item2' ,
html : 'List Item 2' ,
customattr : 'customValue'
}]
});
2. 在實際修改 DOM 結(jié)構(gòu)時,提供兩種方式可供調(diào)用者選擇,以便應需使用:
l W3C 標準 DOM 操作 (appendChild, removeChild) 方式
l 使用 innerHTML,insertAdjacentHTML 等的實效模式
一個具體例子為:
DomBuilder.useDom = true ; // 顯示申明使用 W3C Dom 操作方式
3. 另外提供 DOM 節(jié)點的有效回收方法
var abandoned = DomBuilder.destroy(list); // 將 DOM 節(jié)點從文檔中移除