官术网_书友最值得收藏!

4.3 核心知識(shí)

4.3.1 DOM編程

文檔對(duì)象模型(DOM)是一個(gè)獨(dú)立于語言,用于操作XML和HTML文檔的程序接口(API)。在瀏覽器中,主要用來與HTML文檔打交道,使用DOM API來訪問文檔中的數(shù)據(jù)。盡管DOM是一個(gè)與語言無關(guān)的API,它在瀏覽器中的接口卻是用JavaScript實(shí)現(xiàn)的。由于客戶端腳本編程大多數(shù)時(shí)候是在和底層文檔打交道,因此DOM就成了現(xiàn)在JavaScript編程中的重要部分。

用腳本進(jìn)行DOM操作是Web應(yīng)用中最常見的操作,也會(huì)導(dǎo)致最常見的性能瓶頸。瀏覽器中通常會(huì)把DOM和JavaScript獨(dú)立實(shí)現(xiàn),也就是DOM渲染引擎和JavaScript引擎獨(dú)立,這兩個(gè)相互獨(dú)立的功能通過接口彼此連接,就會(huì)產(chǎn)生消耗。DOM和JavaScript像一個(gè)島嶼,它們之間通過收費(fèi)橋梁連接。ECMAScript每次訪問DOM,都要途徑這座橋梁,并交納過橋費(fèi),訪問的次數(shù)越多,費(fèi)用也就越高。因此,要盡可能減少過橋的次數(shù),盡可能減少DOM操作。

4.3.2 DOM訪問與修改

訪問DOM元素是有代價(jià)的,修改元素則更為昂貴,因?yàn)樗鼤?huì)導(dǎo)致瀏覽器重新計(jì)算頁面的幾何變化。循環(huán)訪問或修改元素,尤其是對(duì)HTML元素集合循環(huán)操作是最壞的情況。

for(var i=0;i<15000;i++){ 
document.getElementById("content").innerHTML+='a'; 
} 

這段代碼循環(huán)修改頁面的內(nèi)容,問題在于每次循環(huán)迭代,該元素都被訪問兩次,一次讀取innerHTML屬性值,另一次重寫它。換一種效率更高的方法,用局部變量存儲(chǔ)修改中的內(nèi)容,在循環(huán)結(jié)束后一次性寫入。

var txt=""; 
for(var i=0;i<15000;i++){ 
txt+="a"; 
} 
document.getElementById("content").innerHTML=txt; 

在所有瀏覽器中,修改后的版本都運(yùn)行得更快。訪問DOM的次數(shù)越多,代碼的運(yùn)行速度越慢。因此,減少訪問DOM的次數(shù),把運(yùn)算盡量留在ECMAScript這一端處理。

4.3.3 DOM遍歷

DOM API提供了多種方法來讀取文檔結(jié)構(gòu)中的特定部分。從某個(gè)DOM元素開始,使用nextSibling來獲取周圍的元素,或者使用childNodes遞歸查找所有子節(jié)點(diǎn)。得到一個(gè)元素節(jié)點(diǎn)后,可以使用parentNode、previousSibling、nextSibling、firstChild、lastChild五個(gè)屬性來找到其他相關(guān)的元素。parentNode在HTML中找到包含該元素的元素節(jié)點(diǎn),也就是該元素的父節(jié)點(diǎn)。previousSibling和nextSibling找到當(dāng)前節(jié)點(diǎn)的前一個(gè)或后一個(gè)兄弟節(jié)點(diǎn)。firstChild和lastChild找到當(dāng)前元素的第一個(gè)或最后一個(gè)子節(jié)點(diǎn)。被找到的節(jié)點(diǎn)是當(dāng)前節(jié)點(diǎn)的屬性,使用時(shí)無須加括號(hào)。當(dāng)被查找的節(jié)點(diǎn)不存在時(shí),返回null。通常情況下nextSibling和childNodes的運(yùn)行時(shí)間幾乎相等,但在IE中,nextSibling比childNodes快得多。因此在性能要求極高時(shí),在老版本的IE中更推薦使用nextSibling方法來查找DOM節(jié)點(diǎn)。

遍歷DOM可能會(huì)遇到瀏覽器的特殊情形,有些瀏覽器(除IE個(gè))會(huì)把元素之間的空白(空格或者換行)當(dāng)作文本節(jié)點(diǎn)來處理,致使childNodes、firstChild、previousSibling和nextSibling并不區(qū)分元素節(jié)點(diǎn)和其他類型節(jié)點(diǎn),比如注釋和文本節(jié)點(diǎn)(可能是兩個(gè)節(jié)點(diǎn)間的空格)。在某些情況下,只需要訪問元素節(jié)點(diǎn),在循環(huán)中需要檢查返回節(jié)點(diǎn)的類型并過濾掉非元素節(jié)點(diǎn),這類檢查和過濾其實(shí)是不必要的DOM操作。解決方法是刪除元素間的空格或者換行,但副作用是代碼閱讀變得困難。另一種方法是使用jQuery來遍歷元素,jQuery內(nèi)部已經(jīng)處理了這種不確定性和兼容性。還有一種方法是采用DOM新屬性來代替,如表4-1所示。

表4-1 DOM遍歷屬性

表4-1列出的屬性都被Firefox 3.5、Safari 4、Chrome以及Opera 9.62所支持,其中IE6、IE7、IE8只支持children屬性。children替代childNodes會(huì)更快,因?yàn)榧享?xiàng)更少。HTML源碼的空白實(shí)際上是文本節(jié)點(diǎn),而且它并不包含在children集合中。

4.3.4 innerHTML對(duì)比DOM方法

在使用innerHTML和document.createElement()創(chuàng)建1000行表格的實(shí)驗(yàn)中,在舊版本的瀏覽器中,innerHTML優(yōu)勢(shì)更加明顯,IE6中的innerHTML比原生的DOM方法快3.6倍,但在新版本中優(yōu)勢(shì)則不很明顯。在基于WebKit內(nèi)核的新版瀏覽器中恰恰相反,DOM方法更高效。所以,最終選擇哪種方式取決于用戶經(jīng)常使用的瀏覽器以及個(gè)人編程習(xí)慣。

在對(duì)性能有苛刻要求的Web應(yīng)用中需要更新一大段HTML,推薦使用innerHTML,因?yàn)樵诮^大多數(shù)瀏覽器中都運(yùn)行得更快,注意字符串合并在老版本IE下性能不佳,使用數(shù)組來合并大量字符串,會(huì)讓innerHTML效率更高。但對(duì)大多數(shù)日常操作而言,并沒有太大區(qū)別,所以Web項(xiàng)目應(yīng)該根據(jù)可讀性、穩(wěn)定性、團(tuán)隊(duì)習(xí)慣、代碼風(fēng)格來綜合決定。另外,也可以通過element.clone Node()克隆已有元素來替代document.createElement()更新頁面內(nèi)容,效率會(huì)更高。

4.3.5 字符串連接

1. +=操作符

+=操作符提供了連接字符串最簡單的方法,事實(shí)上,除了IE7及早期版本外,所有現(xiàn)代瀏覽器都對(duì)它們進(jìn)行了優(yōu)化,所以沒必要尋找其他方法。然而,有些技巧能使操作效率最大化。例如str+="one"+"two"語句,代碼執(zhí)行會(huì)經(jīng)歷四個(gè)步驟:① 在內(nèi)存中創(chuàng)建一個(gè)臨時(shí)字符串;② 連接后的字符串“onetwo”被賦值給臨時(shí)字符串;③ 臨時(shí)字符串與str當(dāng)前值連接;④ 結(jié)果賦值給str。如果改為以下代碼:

str+="one"; 
str+="two"; 

用兩行語句直接附加內(nèi)容給str,從而避免了產(chǎn)生臨時(shí)字符串,或者直接使用str=str+"one"+"two",賦值表達(dá)式由str開始作為基礎(chǔ),每次給它附加一個(gè)字符串,由左向右依次連接,因此避免了使用臨時(shí)字符串,就可以提升性能。

2. 數(shù)組項(xiàng)合并

Array.prototype.join方法將數(shù)組的所有元素合并成一個(gè)字符串,它接受一個(gè)字符串參數(shù)作為分隔符插入每個(gè)元素中間。如果傳入的分隔符為空字符,則將數(shù)組的所有元素連接起來。在大多數(shù)瀏覽器中,數(shù)組項(xiàng)合并比其他字符串連接方法更慢。

var str=" A young idler, an old beggar.少壯不努力,老大徒傷悲"; 
newStr=""; 
appendTimes=10000; 
while(appendTimes--){ 
      newStr+=str; 
} 

改用數(shù)組項(xiàng)合并生成相同的字符串,由于避免了重復(fù)分配內(nèi)存和拷貝逐漸膨脹的字符串,性能明顯提升。當(dāng)把所有數(shù)組元素連接在一起時(shí),瀏覽器會(huì)分配足夠的內(nèi)存來存放整個(gè)字符串,而且不會(huì)多次拷貝最終字符串相同的部分。

var str=" A young idler, an old beggar.少壯不努力,老大徒傷悲"; 
newStr=""; 
appendTimes=10000; 
while(appendTimes--){ 
      str[strs.length]=str; 
} 
newStr=strs.join(""); 

4.3.6 HTML集合length

document.getElementsByName()、document.getElementsByClassName()、document.get ElementsByTagName()、document.images、document.links和document.forms返回值是一個(gè)HTML集合。HTML集合是包含了DOM節(jié)點(diǎn)引用的類數(shù)組對(duì)象,這是一個(gè)類似數(shù)組的列表,但并不是真正的數(shù)組,缺少很多數(shù)組應(yīng)有的方法(比如push()、slice()),但提供了一個(gè)類似數(shù)組中的length屬性,并且能以數(shù)字索引的方式訪問列表中的元素。正如在DOM標(biāo)準(zhǔn)中所定義的HTML集合,當(dāng)?shù)讓游臋n對(duì)象更新時(shí),HTML集合也會(huì)自動(dòng)更新。事實(shí)上,HTML集合一直與文檔保持著連接,每次需要更新信息時(shí),都會(huì)重復(fù)執(zhí)行查詢過程,即便只是獲取集合中元素的個(gè)數(shù)。

var alldivs=document.getElementsByTagName("div"); 
for(var i=0;i<alldivs.length;i++){ 
      document.body.appendChild(document.createElement("div")); 
} 

以上代碼遍歷現(xiàn)有的div元素,每次創(chuàng)建一個(gè)新的div并添加到body中,因?yàn)檠h(huán)退出條件alldivs.length在每次迭代時(shí)都會(huì)增加,反映了底層文檔的當(dāng)前狀態(tài),事實(shí)上形成了一個(gè)死循環(huán)。在循環(huán)的條件控制語句中讀取HTML集合的length屬性是不推薦的做法,讀取一個(gè)HTML集合的length比讀取普通數(shù)組的length慢得多,因?yàn)槊看味家匦虏樵儭?yōu)化的方法很簡單,把集合的長度緩存到一個(gè)局部變量中,然后在循環(huán)的條件退出語句中使用該變量。

var coll=document.getElementsByTagName("div"); 
var len=coll.length; 
name=''; 
el=null; 
for(var i=0;i<len;i++){ 
      el=coll[i]; 
      name=el.nodeName; 
      name+=el.nodeType; 
      name+=el.tagName; 
} 
document.getElementById("content").innerHTML=name; 

通過局部變量引用能提升速度,在多次讀取時(shí),緩存集合能進(jìn)一步提升性能。

4.3.7 減少瀏覽器重排與重繪

瀏覽器下載完HTML、CSS、JS后會(huì)生成兩棵樹:DOM樹和渲染樹。當(dāng)DOM的幾何屬性(比如DOM的寬高、顏色、position)發(fā)生變化時(shí),瀏覽器需要重新計(jì)算元素的幾何屬性,并且重新構(gòu)建渲染樹,這個(gè)過程稱之為重繪重排,示例代碼如下:

bodystyle=document.body.style; 
bodystyle.color=red; 
bodystyle.height=1000px; 
bodystyle.width=100%;

上述方式修改三個(gè)屬性,瀏覽器會(huì)進(jìn)行三次重排重繪,在某些情況下,減少這種重排可以提高瀏覽器的渲染性能。推薦方式如下,只進(jìn)行一次操作,完成三個(gè)步驟:

bodystyle=document.body.style; 
bodystyle.cssText='color:red;height:1000px;width:100%'; 

重排指瀏覽器重新構(gòu)造渲染樹的過程,重繪指瀏覽器將重排后的渲染樹繪制到屏幕的過程。導(dǎo)致重排的情況有:①添加或刪除可見的DOM元素;②元素位置改變;③元素尺寸(外邊距、內(nèi)邊距、邊框厚度、寬度、高度等屬性)改變;④內(nèi)容改變,如文本改變和圖片被替代;⑤頁面渲染初始化;瀏覽器窗口尺寸改變。最小化重繪和重排可以提高程序的響應(yīng)速度。以下代碼可能會(huì)導(dǎo)致瀏覽器觸發(fā)三次重排,效率低下。3次訪問DOM可以被優(yōu)化。

el.style.borderLeft="1px"; 
el.style.borderRight="2px"; 
el.style.padding="5px"; 

1. cssText屬性

通過合并所有的改變、然后一次性處理,這樣只會(huì)修改DOM一次,使用cssText屬性可以實(shí)現(xiàn),代碼如下:

el.style.cssText="border-left:1px;border-right:2px;padding:5px"; 

cssText屬性會(huì)覆蓋已經(jīng)存在的樣式信息,因此想保留現(xiàn)有樣式,可以把它附加在cssText字符串后面,改為:

el.style.cssText+=";border-left:1px;border-right:2px;padding:5px"; 

另外一種一次性修改樣式的辦法是修改元素的樣式名稱,而不是修改內(nèi)聯(lián)樣式,這種方法適合那些不依賴運(yùn)行邏輯和計(jì)算的情況。改變CSS的class名稱的方法更清晰,更易于維護(hù),有助于保持腳本與HTML分離。

2. 批量修改DOM

當(dāng)需要對(duì)DOM元素進(jìn)行一系列操作時(shí),可以通過使元素脫離文檔流、對(duì)其應(yīng)用批量修改和把元素帶回文檔3個(gè)步驟,減少因批量修改過程導(dǎo)致的多次重排操作。使元素脫離文檔可以采用隱藏元素、文檔片斷(document fragment)和修改副本再替換三種方式。

減少重排的第一種方法:將要修改的區(qū)域先設(shè)置display屬性隱藏起來,臨時(shí)從文檔中移除元素,修改完畢后再恢復(fù)顯示。

<ul id="mylist"></ul> 
 
var data = [ 
      { 
            "name": "百度", 
            "url": "http://www.baidu.com" 
      }, 
      { 
            "name": "騰訊", 
            "url": "http://www.qq.com" 
      } 
]; 
 
function apendDataToElement(appendToElement, data) { 
      var a, li; 
      for (var i = 0, max = data.length; i < max; i++) { 
            a = document.createElement("a"); 
            a.href = data[i].url; 
            a.appendChild(document.createTextNode(data[i].name)); 
            li = document.createElement("li"); 
            li.appendChild(a); 
            appendToElement.appendChild(li); 
      } 
} 
var ul = document.getElementById("mylist"); 
ul.style.display = "none"; 
apendDataToElement(ul, data); 
ul.style.display = "block";  

減少重排的第二種方法:在文檔之外創(chuàng)建并更新一個(gè)文檔片斷,然后把它附加到原始列表中。文檔片段是一個(gè)輕量級(jí)的document對(duì)象,其設(shè)計(jì)初衷是為了完成更新和移動(dòng)節(jié)點(diǎn)的任務(wù)。當(dāng)附加一個(gè)片段到節(jié)點(diǎn)時(shí),實(shí)際上被添加的是該片斷的子節(jié)點(diǎn),而不是片段本身。以下例子少一行代碼,只觸發(fā)一次重排,而且只訪問一次實(shí)時(shí)的DOM。

var fragment=document.createDocumentFragment(); 
apendDataToElement(fragment,data); 
document.getElementById("mylist").appendChild(fragment); 

減少重排的第三種方法:為需要修改的節(jié)點(diǎn)創(chuàng)建一個(gè)備份,然后對(duì)副本進(jìn)行操作,一旦操作完成,就用新的節(jié)點(diǎn)替換舊的節(jié)點(diǎn)。

var old=document.getElementById("mylist"); 
var clone=old.cloneNode(true); 
apendDataToElement(clone,data); 
old.parentNode.replaceChild(clone,old); 

推薦使用文檔片斷方案,該方案所產(chǎn)生的DOM遍歷和重排次數(shù)最少,唯一潛在的問題是文檔片斷未被充分利用,有些團(tuán)隊(duì)成員可能并不熟悉這項(xiàng)技術(shù)。

3. 緩存布局信息

瀏覽器嘗試通過隊(duì)列化修改和批量執(zhí)行的方式最小化重排次數(shù)。當(dāng)查詢布局信息時(shí),比如偏移量(offsetLeft、offsetTop等)、滾動(dòng)位置(scrollTop、scrollLeft)或是計(jì)算樣式值時(shí),瀏覽器為了返回最新值,會(huì)刷新隊(duì)列并應(yīng)用所有變更。最好的做法是盡量減少布局信息的獲取次數(shù),獲取后把它緩存給局部變量,然后再操作局部變量。

var ul=document.getElementById("content"); 
ul.style.left=ul.offsetLeft+1+"px"; 
ul.style.top=ul.offsetTop+1+"px"; 
if(ul.offsetLeft>=500){ 
      stopAnimation(); 
}

這種方法效率低下,因?yàn)樵孛看我苿?dòng)時(shí)都會(huì)查詢偏移量,導(dǎo)致瀏覽器刷新渲染隊(duì)列而不利于優(yōu)化。優(yōu)化后的方法是將獲取起始位置的值賦值給一個(gè)變量,然后在動(dòng)畫循環(huán)中直接使用變量,而不再查詢偏移量。

var current=ul.offsetLeft; 
current++; 
ul.style.left=current+"px"; 
if(current>=500){ 
      stopAnimation(); 
} 

4. 讓元素脫離動(dòng)畫流

瀏覽器所需要重排的次數(shù)越少,應(yīng)用程序的響應(yīng)速度就越快。例如,當(dāng)頁面頂部的一個(gè)動(dòng)畫推移至頁面整個(gè)余下的部分時(shí),會(huì)導(dǎo)致一次昂貴的大規(guī)模重排,讓用戶感到頁面一頓一頓的。渲染樹中需要重新計(jì)算的節(jié)點(diǎn)越多,情況越糟。避免頁面中的大部分重排的方法是使用絕對(duì)位置定位頁面上的動(dòng)畫元素,將其脫離文檔流,讓元素動(dòng)起來,當(dāng)動(dòng)畫結(jié)束時(shí)恢復(fù)定位,從而只會(huì)下移一次文檔的其他元素。

主站蜘蛛池模板: 琼结县| 普洱| 蒙城县| 台南市| 洛宁县| 渭源县| 太仆寺旗| 手游| 集贤县| 西华县| 获嘉县| 赫章县| 隆昌县| 合水县| 萨迦县| 蒲江县| 天等县| 蓬溪县| 建始县| 郓城县| 武城县| 兰西县| 吴桥县| 桐庐县| 视频| 上虞市| 汪清县| 南投县| 梁平县| 蒙城县| 金沙县| 塔河县| 水富县| 江阴市| 阿坝| 临沧市| 右玉县| 信阳市| 宁强县| 中牟县| 呼玛县|