頻繁地對於DOM進行操作的很是損耗效能,但在富網頁應用中我們編寫指令碼無可避免地要跟DOM打交道,到底怎麼才能最佳化這個效能瓶頸呢,大致從以下三種情況去考慮:
- 訪問和修改DOM元素
- 修改DOM樣式,會造成頁面的重繪和重新排版
- 通過DOM事件處理常式來響應使用者
訪問和修改DOM元素
在瀏覽器中,DOM的實現和Javascript的實現通常是保持相互獨立的。下面瞭解一下主流瀏覽器的渲染引擎和JS引擎:
瀏覽器 |
渲染引擎(核心) |
JS引擎 |
IE |
mshtml.dll(Trident) |
JScript |
Chrome |
WebCore(WebKit) |
V8 |
FireFox |
Gecko |
Spider-Monkey/TraceMonkey |
Safari |
WebCore(WebKit) |
JavaScriptCore/SquirrelFish |
為什麼訪問DOM對效能有影響?那是因為兩個獨立的部分通過他們各自的介面來串連就會帶來效能損耗。打個比喻,把DOM看成一個島嶼,把Javascript看成另外一個島嶼,兩者之間以一座要收費的橋串連,每次Javascript訪問DOM都需要過橋,交一次橋費。來回多了費用自然就高了。所以我們得想方設法減少過橋的次數。
訪問DOM元素的代價就是交一次“橋費”,修改DOM元素則會導致瀏覽器重新計算頁面的幾何變化。如果是迴圈修改DOM元素,其代價可想而知。如下代碼:
function innerHTMLLoop1(){ for(var count=0;count<10000;count++){ document.getElementById("test").innerHTML +="增加內容"; }}
在這段代碼中,每迴圈一次都要對DOM元素訪問兩次:一次是讀取innerHTML 屬性的內容,另一次是把新的內容寫入它。所以一個最佳化的辦法就是使用一個局部變數儲存更新後的內容,在迴圈結束時再一次性寫入:
function innerHTMLLoop2(){
var content =""; for(var count=0;count<10000;count++){ content +="增加內容"; } document.getElementById("test").innerHTML += content ;}
很明顯,innerHTMLLoop2交的“橋費”明顯要少,因為它只訪問了兩次DOM元素,一次讀入,一次寫入。所以一般的法則是:盡量在自己的範圍內(Javascript島嶼)活動,別經常到外面鬼混去。(據說,在廣州,月薪不到5K的別交女朋友,你懂的!)。
更新頁面的兩種方法效能比較:innerHTML和DOM方法(如document.creatElment)。《高效能Javascript編程》的答案是:效能差別不大,innerHTML好一些,使用簡單嘛。另外還有一個更新頁面的方法是節點複製--element.cloneNode()。
HTML集合的操作
HTML集合是用於存放DOM節點引用的類數組對象。可通過下列的方法或屬性得到這樣的集合:
- document.getElementsByName()
- document.getElementsByTagName()
- document.getElementsByClassName()
- document.images 返回對文檔中所有 Image 對象引用
- document.links 返回對文檔中所有 Area 和 Link 對象引用
- document.forms 返回對文檔中所有 Form 對象引用
- document.forms[0].elements 返回對文檔中第一個表單的所有元素
HTML集合會即時查詢文檔資訊,也就是說當你要用到這個集合時,它會自動查詢文檔的最新資訊。請看如下代碼:
1 var allDivs = document.getElementsByTagName("div");2 for(var i=0;i<allDivs.length;i++){3 document.body.appendChild(document.createElement("div"));4 }
例如像上面的那段代碼其實是個死迴圈,因為每一次訪問div集合的length屬性,它都會重新計算文檔中的div元素數目。這就是html集合低效率的來源。要改進代碼就用一個局部變數儲存div集合的length屬性: for(var i=0, len=allDivs.length; i<len; i++){...}
訪問HTML集合的length比數組的length要慢,所以要訪問這種集合類的數目length,我們都應該先用一個局部變數去儲存它:var len = 集合.length;
另外,訪問數組的元素要比訪問HTML集合的元素要快。所以我們可以先把HTML集合轉換成數組才去進行相應的操作:
//HTML集合轉換成數組function toArray(coll){ for(var a=[], i=0, len=coll.legnth; i<len; i++){ a[i] = coll[i]; } return a;}//使用var coll = document.getElementsByTagName("div");var divs = toArray(coll);
有人可能會問,這樣多用了一個數組副本到底值不值得?這個倒是要看情況吧。不過另外一種選擇,使用局部變數:
function loopColletion(){ var coll = document.getElmentsByTagName("div"), len = coll.length, el = null; for(var i = 0; i<len; i++){ el = coll[i]; //然後訪問局部變數el }}
許多瀏覽器提供了API函數返回元素節點,這些API都是原生的,所以可用的話就盡量用。列舉了一些DOM的屬性:
列舉的所有屬效能被FF,safari,chrome,opera所支援,ie6-8隻支援children。
遍曆children比childNodes更快,因為集合項目少了。HTML源碼中的空格實際上是文本節點,但他們不包含在children中。
另外還有兩個比較好的選取器API:document.querySelectorAll()和document.querySelector()。前者接收一個CSS選取器字串參數並返回一個NodeList類數組對象而不是返回HTML集合,後者只返回符合查詢條件的第一個節點。很遺憾IE6、7不支援這兩個API。
PS:寫一篇博文不容易啊,花了好幾個小時,不過可以鞏固一下知識,這是一個好習慣。這個部落格就是我的學習筆記,O(∩_∩)O哈哈~