正巧看到在送書,于是乎找了找自己博客上記錄過的一些東西來及其無恥的蹭書了~~~
小廣告:更多內(nèi)容可以看我的博客
JavaScript的DOM操作可以說是JavaScript最重要的功能,我們經(jīng)常要根據(jù)用戶的操作來動態(tài)的增加和刪除元素,或是通過AJAX返回的數(shù)據(jù)動態(tài)生成元素。比如我們獲得了一個很多元素的數(shù)組data[]
,需要將其每個值生成一個li元素插入到一個id為container的ul元素中,最簡單(最慢)的方式是:
var liNode, i, m;for (i = 0, m = data.length; i < m; i++) { liNode = document.createElement("li"); liNode.innerText = data[i]; document.getElementById("container").appendChild(liNode);}
這里每一次循環(huán)都會去查找id為container的元素,效率自然非常低,所以我們需要將元素在循環(huán)前查詢完畢,在循環(huán)中僅僅是引用就行了,修改代碼為:
var ulNode = document.getElementById("container");var liNode, i, m;for (i = 0, m = data.length; i < m; i++) { liNode = document.createElement("li"); liNode.innerText = data[i]; ulNode.appendChild(liNode);}
緩存DOM對象的方式也經(jīng)常被用在元素的查找中,查找元素應(yīng)該是DOM操作中最頻繁的操作了,其效率優(yōu)化也是大頭。在一般情況下,我們會根據(jù)需要,將一些頻繁被查找的元素緩存起來,在查找它或查找它的子孫元素時,以它為起點進行查找,就能提高查找效率了。
由于DOM操作會導(dǎo)致瀏覽器的回流,回流需要花費大量的時間進行樣式計算和節(jié)點重繪與渲染,所以應(yīng)當盡量減少回流次數(shù)。一種可靠的方法就是加入元素時不要修改頁面上已經(jīng)存在的元素,而是在內(nèi)存中的節(jié)點進行大量的操作,最后再一并將修改運用到頁面上。DOM操作本身提供一個創(chuàng)建內(nèi)存節(jié)點片段的功能:document.createDocumentFragment()
,我們可以將其運用于上述代碼中:
var ulNode = document.getElementById("container");var liNode, i, m;var fragment = document.createDocumentFragment();for (i = 0, m = data.length; i < m; i++) { liNode = document.createElement("li"); liNode.innerText = data[i]; fragment.appendChild(liNode);}ulNode.appendChild(fragment);
這樣就只會觸發(fā)一次回流,效率會得到很大的提升。如果需要對一個元素進行復(fù)雜的操作(刪減、添加子節(jié)點),那么我們應(yīng)當先將元素從頁面中移除,然后再對其進行操作,或者將其復(fù)制一個(cloneNode()
),在內(nèi)存中進行操作后再替換原來的節(jié)點
在這里我們每次都需要生成節(jié)點(document.createElement("li")
),然后將其加入到內(nèi)存片段中,我們可以通過innerHTML
屬性來一次性生成節(jié)點,具體的思路就是使用字符串拼接的方式,先生成相應(yīng)的HTML字符串,最后一次性寫入到ul的innerHTML中。修改代碼為:
var ulNode = document.getElementById("container");var fragmentHtml = "", i, m;for (i = 0, m = data.length; i < m; i++) { fragmentHtml += "<li>" + data[i] + "</li>";}ulNode.innerHTML = fragmentHtml;
這樣效率也會有提升,不過手動拼寫字符串是相當麻煩的一件事
有時候我們需要通過JavaScript給元素增加樣式,比如如下代碼:
element.style.fontWeight = 'bold';element.style.backgroundImage = 'url(back.gif)';element.style.backgroundColor = 'white';element.style.color = 'white';//...
這樣效率很低,每次修改style屬性后都會觸發(fā)元素的重繪,如果修改了的屬性涉及大小和位置,將會導(dǎo)致回流。所以我們應(yīng)當盡量避免多次為一個元素設(shè)置style屬性,應(yīng)當通過給其添加新的CSS類,來修改其CSS
.element { background-image: url(back.gif); background-color: #fff; color: #fff; font-weight: 'bold'; /*...*/}
element.className += " element";
還是之前那個ul和添加li,如果我們需要給每個li都綁定一個click事件,就可能寫出類似如下代碼:
var ulNode = document.getElementById("container");var fragment = document.createDocumentFragment();var liNode, i, m;var liFnCb = function(evt){ //do something};for (i = 0, m = data.length; i < m; i++) { liNode = document.createElement("li"); liNode.innerText = data[i]; liNode.addEventListener("click", liFnCb, false); fragment.appendChild(liNode);}ulNode.appendChild(fragment);
這里每個li元素都需要執(zhí)行一次addEventListener()
方法,如果li元素數(shù)量一多,就會降低效率。所以我們可以通過事件代理的方式,將事件綁定在ul上,然后通過event.target
來確定被點擊的元素是否是li元素,同時我們也可以使用innerHTML
屬性一次性創(chuàng)建節(jié)點了,修改代碼為:
var ulNode = document.getElementById("container");var fragmentHtml = "", i, m;var liFnCb = function(evt){ //do something};for (i = 0, m = data.length; i < m; i++) { fragmentHtml += "<li>" + data[i] + "</li>";}ulNode.innerHTML = fragmentHtml;ulNode.addEventListener("click", function(evt){ if(evt.target.tagName.toLowerCase() === 'li') { liFnCb.call(evt.target, evt); }}, false);
這樣事件綁定的代碼就只要執(zhí)行一次,可以監(jiān)聽所有l(wèi)i元素的事件了。當然如果需要移除事件回調(diào)函數(shù),我們也不需要循環(huán)遍歷所有的li元素,只需要移除ul元素上的事件處理就行了