摘要:優(yōu)化策略跟上面的大同小異,就是用局部變量緩存集合以及集合的長(zhǎng)度,我就不進(jìn)行實(shí)際測(cè)試了。例如錯(cuò)誤的做法使用修改來進(jìn)行優(yōu)化如果需要?jiǎng)討B(tài)修改,那么就使用批量處理操作并且讓元素脫離文檔流,等操作結(jié)束后再放回文檔流中。
上篇我介紹了Javascript標(biāo)識(shí)符查找方面的優(yōu)化,可以看出在這方面的優(yōu)化給性能帶來的提升并不明顯,甚至可以說基本沒有影響。但是,我今天要分享的是前端Javascript優(yōu)化的一個(gè)大頭。眾所周知,在瀏覽器端Javascript中DOM操作相比普通Javascript代碼來說是比較耗時(shí)的,所以在DOM優(yōu)化上下功夫可以收到相當(dāng)可觀的性能優(yōu)化。下面我將分享幾個(gè)DOM方面的性能優(yōu)化策略。
耗時(shí)的DOM操作瀏覽器中的Javascript可以分為兩個(gè)部分:ECMAScript和DOM API。而相比原生的ECMAScript來說,DOM API會(huì)耗時(shí)很多。我們可以把這兩部分想象成兩個(gè)通過橋梁連接的小島,在ECMAScript小島上進(jìn)行的操作運(yùn)行速度比在DOM小島上面的操作要快很多,每次在進(jìn)行DOM操作的時(shí)候你都需要從ECMAScript這個(gè)小島通過這個(gè)橋梁到達(dá)DOM小島上然后在上面進(jìn)行耗時(shí)的操作。所以大量的DOM操作就會(huì)降低性能。
大家先看看下面這個(gè)例子:
//優(yōu)化前 var start = new Date().getTime() ; for(var i = 0 ; i < length ; i ++){ document.getElementById("test").innerHTML += "a" ; } console.log("Before:" + (new Date().getTime() - start)) ; //優(yōu)化后 start = new Date().getTime() ; var content = "" ; for(var i = 0 ; i < length ; i ++){ content += "a" ; } document.getElementById("test").innerHTML += content ; console.log("After:" + (new Date().getTime() - start)) ;
從運(yùn)行結(jié)果來看,可以說差距那是相當(dāng)明顯?。?/p>
優(yōu)化前的代碼每一次循環(huán)都進(jìn)行了DOM操作,而優(yōu)化之后,只在最后一步進(jìn)行了DOM操作,這就是DOM優(yōu)化的力量啊。所以,我們應(yīng)該在操作的時(shí)候盡量避免對(duì)DOM的操作,能少操作DOM就少操作。按照上面的比喻就好比是,我們通過橋梁從ECMAScript小島到達(dá)DOM小島,然后找出需要進(jìn)行操作的元素,把它再帶回到ECMAScript小島進(jìn)行操作,通過這個(gè)方式,可以加快操作的速度,我們應(yīng)該盡可能多的把元素帶回到ECMAScript小島進(jìn)行操作。
innerHTML還是createElement在頁面上動(dòng)態(tài)添加結(jié)點(diǎn)一般有兩個(gè)方法:innerHTML和createElement方法。這兩個(gè)方法在性能上也有一點(diǎn)差別,具體差別在哪兒呢?上代碼:
var start = new Date().getTime() ; var content = "" ; for(var i = 0 ; i < 1000 ; i ++){ content += "" ; } content += "" ; document.getElementById("test").innerHTML += content ; console.log("innerHTML:" + (new Date().getTime() - start)) ; document.getElementById("test").innerHTML = "" ; start = new Date().getTime() ; //為了避免直接往test節(jié)點(diǎn)上面添加節(jié)點(diǎn)引起的頁面重畫,所以使用一個(gè)div節(jié)點(diǎn)來存儲(chǔ)添加的節(jié)點(diǎn),最后把div添加到頁面中 var div = document.createElement("div") ; for(var i = 0 ; i < 1000 ; i ++){ div.appendChild(document.createElement("div")) ; } document.getElementById("test").appendChild(div) ; console.log("createElement:" + (new Date().getTime() - start)) ;
這段代碼在不同瀏覽器上的運(yùn)行結(jié)果是不一樣的:
在Chrome上createElement比innerHTML快,而在Firefoxhe和IE上結(jié)果則相反,從結(jié)果上看似乎是innerHTML以2:1贏了,可是我還是建議大家使用createElement,我把上面的代碼改成下面這樣:
var start = new Date().getTime() ; var test = document.getElementById("test") ; for(var i = 0 ; i < 1000 ; i ++){ test.innerHTML += "" ; } console.log("innerHTML:" + (new Date().getTime() - start)) ; document.getElementById("test").innerHTML = "" ; start = new Date().getTime() ; for(var i = 0 ; i < 1000 ; i ++){ test.appendChild(document.createElement("div")) ; } console.log("createElement:" + (new Date().getTime() - start)) ;
上面這段代碼的運(yùn)行結(jié)果
可以看出來innerHTML和createElement差很多。為了測(cè)試我用了比較大的數(shù)據(jù)1000,在實(shí)際開發(fā)中一般不會(huì)出現(xiàn)這種情況,所以性能上的差異也就不會(huì)那么明顯,但是除了考慮性能問題以外,我們還應(yīng)該考慮代碼的可讀性以及可維護(hù)下方面的問題,而考慮到這些的話,我個(gè)人還是比較推薦使用createElement,如果大家有什么別的看法,歡迎一起討論。
HTMLCollectionHTMLCollection是若干個(gè)DOM節(jié)點(diǎn)的集合,它具有數(shù)組的一些特性,比如length屬性、通過下標(biāo)訪問,但是它并不是數(shù)組,它沒有push和slice方法。在DOM操作中我們經(jīng)常會(huì)用到HTMLCollection,下面的方法都會(huì)返回HTMLCollection:
getElementsByName
getElementsByTagName
getElementsByClassName
document.forms
document.images
document.links
還有一些別的方法和屬性會(huì)返回HTMLCollection,在這里就不一一列舉了。如何處理它們也是影響性能的一個(gè)方面。優(yōu)化策略跟上面的大同小異,就是用局部變量緩存集合以及集合的長(zhǎng)度,我就不進(jìn)行實(shí)際測(cè)試了。HTMLCollection還有一個(gè)很重要的特性就是它是根據(jù)頁面的情況動(dòng)態(tài)更新的,如果你更新的頁面那么它的內(nèi)容也會(huì)發(fā)生變化。比如下面這段代碼:
var divs = document.getElementsByTagName("div") ; for(var i = 0 ; i < divs.length ; i ++){ document.body.appendChild(document.createElement("div")) ; }
這段代碼的原意是向body中添加多一倍的div節(jié)點(diǎn),但是真正的運(yùn)行會(huì)導(dǎo)致死循環(huán),這就是因?yàn)閐ivs是動(dòng)態(tài)更新的,每次向body中添加div節(jié)點(diǎn)都會(huì)使length屬性發(fā)生變化也就是加1,所以這個(gè)循環(huán)會(huì)一直執(zhí)行下去,在開發(fā)的時(shí)候應(yīng)該注意這個(gè)問題。一個(gè)理想的辦法就是緩存divs的長(zhǎng)度,這樣就不會(huì)引起死循環(huán)了。
節(jié)點(diǎn)篩選如果需要得到某個(gè)節(jié)點(diǎn)的所以孩子節(jié)點(diǎn),我們可能會(huì)用到childNodes屬性;得到第一個(gè)孩子,我們可能會(huì)用到firstChild;得到下一個(gè)兄弟節(jié)點(diǎn),我們可能會(huì)用到nextSibling。但是這些屬性都存在一些問題就是它們會(huì)把一些空格和空行也當(dāng)作孩子節(jié)點(diǎn)返回給我們,而這些經(jīng)常不是我們所想要的,如果使用這些屬性那么我們就需要對(duì)它們進(jìn)行篩選,這樣勢(shì)必會(huì)影響效率。所以我們應(yīng)該用別的屬性來替代這些,看下表:
表格左邊的是推薦的屬性,它們只會(huì)返回Element節(jié)點(diǎn)。不過并不是所有瀏覽器都支持,所以在使用之前我們需要先判斷一下。
使用選擇器方法替代傳統(tǒng)方法現(xiàn)代瀏覽器給我們提供了另外一種方法在獲取我們需要的節(jié)點(diǎn),這個(gè)方法是querySelectorAll和querySelector。它們通過CSS選擇器作為參數(shù),返回滿足條件的節(jié)點(diǎn)。querySelectorAll方法返回滿足條件的所有節(jié)點(diǎn)而querySelector返回滿足條件的第一個(gè)節(jié)點(diǎn)。使用這兩個(gè)方法來替代我們以前經(jīng)常用的getElementById,getElementsByTagName等方法也是提高性能的一個(gè)途徑。不過還是老問題,并不是所有瀏覽器都支持這兩個(gè)方法,所有還是先做個(gè)判斷吧。
Reflow 和 Repaint首先,Repaint是指頁面上的元素的外觀發(fā)生了改變但是不影響布局的情況下引起的瀏覽器重新繪畫元素外觀的行為,比如修改color,background-color等屬性。Reflow是指頁面上的元素的大小布局發(fā)生的變化從而引起瀏覽器對(duì)頁面其他元素位置大小進(jìn)行重新計(jì)算并且布局的行為。Reflow所導(dǎo)致的性能消耗遠(yuǎn)比Repaint大,所以我們下面重點(diǎn)討論Reflow情況下的優(yōu)化策略。
在討論Reflow之前先簡(jiǎn)單的看一下瀏覽器加載頁面的過程。如下圖:
瀏覽器在收到HTML文檔之后對(duì)其進(jìn)行解析,解析過程分為兩個(gè)部分DOM文檔的解析和CSS樣式的解析。解析DOM文檔生成一個(gè)DOM樹,DOM樹和解析出來的CSS樣式組合生成一個(gè)渲染樹,最后瀏覽器根據(jù)這個(gè)渲染樹進(jìn)行頁面的排版和繪畫。而最后這一步就是會(huì)涉及到Reflow和Repaint。
以下這幾個(gè)行為會(huì)引起頁面的Reflow或Repaint:
添加,刪除,更新DOM節(jié)點(diǎn)
隱藏/顯示DOM節(jié)點(diǎn)(display:none或visibility:hidden)
修改樣式
改變窗口大小,滾動(dòng)頁面
其實(shí)瀏覽器在這方面已經(jīng)幫我們做了一些優(yōu)化了,對(duì)于每個(gè)觸發(fā)Reflow的行為瀏覽器并不會(huì)馬上就觸發(fā),而是把它們保存在一個(gè)隊(duì)列中,當(dāng)?shù)竭_(dá)一定數(shù)量的時(shí)候再進(jìn)行批量的Reflow,這樣就不需要每次都進(jìn)行Reflow。但是,我們的一些行為會(huì)影響到瀏覽器的優(yōu)化,使得Reflow馬上觸發(fā)。當(dāng)我們請(qǐng)求下面這些屬性的時(shí)候發(fā)生這種現(xiàn)象:
offsetTop, offsetLeft, offsetWidth, offsetHeight
scrollTop/Left/Width/Height
clientTop/Left/Width/Height
getComputedStyle(), or currentStyle(IE)
每當(dāng)我們請(qǐng)求這些屬性時(shí),瀏覽器為了返回實(shí)時(shí)的情況就必須馬上進(jìn)行Reflow以計(jì)算出我們所需要的屬性。所以我們應(yīng)該盡量少的使用這些屬性。
從上面可以發(fā)現(xiàn),基于所有DOM操作都會(huì)引起Reflow或Repaint,所以盡可能避免頁面的Reflow或Repaint可以很好的提高DOM性能。那么該怎么做才能最好的避免或最小化Reflow呢?下面有幾個(gè)有用的建議:
1.不要逐一修改樣式,而改為通過修改className來批量改變樣式,如果樣式需要?jiǎng)討B(tài)計(jì)算,那么也要使用cssText屬性來批量添加樣式。例如:
// 錯(cuò)誤的做法 var left = 10, top = 10; el.style.left = left + "px"; el.style.top = top + "px"; // 使用修改className來進(jìn)行優(yōu)化 el.className += " theclassname"; // 如果需要?jiǎng)討B(tài)修改css,那么就使用cssText el.style.cssText += "; left: " + left + "px; top: " + top + "px;";
2.批量處理DOM操作并且讓元素脫離文檔流,等操作結(jié)束后再放回文檔流中。有以下幾種辦法:
使用display:none隱藏element,然后進(jìn)行操作,最后再顯示出來
使用documentFragment ,把新增的節(jié)點(diǎn)放在documentFragment中,最后再把documentFragment放到DOM中,因?yàn)榘裠ocumentFragment放到DOM中,它只會(huì)把它的孩子節(jié)點(diǎn)放到DOM中,就好像documentFragment不存在。
通過cloneNode復(fù)制節(jié)點(diǎn),然后離線進(jìn)行操作,最后再替換DOM中的節(jié)點(diǎn)。
3.盡量少的訪問會(huì)引起馬上Reflow的屬性,使用局部變量來緩存這些屬性,比如:
var left = el.offsetLeft, top = el.offsetTop esty = el.style; for(big; loop; here) { left += 10; top += 10; esty.left = left + "px"; esty.top = top + "px"; }
4.對(duì)于需要?jiǎng)赢嫷脑?,盡量讓它脫離文檔流,這樣就能盡量引起盡量小的Reflow
5.盡量少使用table布局
事件代理事件代理我想這個(gè)大家應(yīng)該都知道了。越多的事件綁定頁面就加載越慢并且占用更多內(nèi)存,同時(shí)綁定太多事件也會(huì)使得代碼的可讀性降低。使用事件代理的方法原理就是把事件綁定到元素的父節(jié)點(diǎn),然后在處理函數(shù)中判斷target,根據(jù)不同的target執(zhí)行不同的邏輯。這樣能很大程度的減少綁定是事件數(shù)量并且提高代碼的簡(jiǎn)潔度。
總結(jié)看了這么多其實(shí)總結(jié)起來還是比較簡(jiǎn)單的,在進(jìn)行DOM操作的時(shí)候盡量把DOM操作轉(zhuǎn)換為本地的Javascript操作,使用時(shí)先緩存一些DOM元素或者屬性,緩存長(zhǎng)度。在需要進(jìn)行大量DOM操作的時(shí)候,先讓元素脫離文檔,等操作結(jié)束再把元素放回文檔中。優(yōu)化策略還是需要在實(shí)踐中不斷嘗試,不斷摸索,找出最優(yōu)的解決方案。
最近準(zhǔn)備畢設(shè)沒什么時(shí)間更新博客,后面盡量安排好時(shí)間做到一周一篇,前端優(yōu)化Javascript篇未完待續(xù)。。。
原文地址:
http://lakb248.github.io/2014/06/13/optimization_of_front-end--javascript(4optimization_of_dom)/
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/87545.html
摘要:從本篇博客開始,我會(huì)跟大家分享下我關(guān)于前端優(yōu)化方面的學(xué)習(xí),由于時(shí)間原因每篇博客只能分享一小點(diǎn)內(nèi)容,一點(diǎn)點(diǎn)深入前端優(yōu)化的細(xì)節(jié)。在前端優(yōu)化這個(gè)問題上,最被大家熟知的應(yīng)該就是雅虎前端優(yōu)化條軍規(guī)以及雅虎前端優(yōu)化條規(guī)則。 從本篇博客開始,我會(huì)跟大家分享下我關(guān)于前端優(yōu)化方面的學(xué)習(xí),由于時(shí)間原因每篇博客只能分享一小點(diǎn)內(nèi)容,一點(diǎn)點(diǎn)深入前端優(yōu)化的細(xì)節(jié)?! ∽鲞^前端的人都知道,前端優(yōu)化是一個(gè)永遠(yuǎn)都不會(huì)...
摘要:特意對(duì)前端學(xué)習(xí)資源做一個(gè)匯總,方便自己學(xué)習(xí)查閱參考,和好友們共同進(jìn)步。 特意對(duì)前端學(xué)習(xí)資源做一個(gè)匯總,方便自己學(xué)習(xí)查閱參考,和好友們共同進(jìn)步。 本以為自己收藏的站點(diǎn)多,可以很快搞定,沒想到一入?yún)R總深似海。還有很多不足&遺漏的地方,歡迎補(bǔ)充。有錯(cuò)誤的地方,還請(qǐng)斧正... 托管: welcome to git,歡迎交流,感謝star 有好友反應(yīng)和斧正,會(huì)及時(shí)更新,平時(shí)業(yè)務(wù)工作時(shí)也會(huì)不定期更...
摘要:前端性能優(yōu)化總結(jié)資源優(yōu)化緩存最好的資源優(yōu)化就是不加載資源。緩存主要分為強(qiáng)制緩存和協(xié)商緩存。的值為服務(wù)端返回的數(shù)據(jù)到期時(shí)間。的使用教程為了保證正常的,有些渲染性能優(yōu)化還是有必要的。 前端性能優(yōu)化總結(jié) 資源優(yōu)化 緩存 最好的資源優(yōu)化就是不加載資源。緩存也是最見效的優(yōu)化手段。說實(shí)話,雖然說客戶端緩存發(fā)生在瀏覽器端,但緩存主要還是服務(wù)端來控制,與我們前端關(guān)系并不是很大。但還是有必要了解一下。 ...
摘要:前端性能優(yōu)化總結(jié)資源優(yōu)化緩存最好的資源優(yōu)化就是不加載資源。緩存主要分為強(qiáng)制緩存和協(xié)商緩存。的值為服務(wù)端返回的數(shù)據(jù)到期時(shí)間。的使用教程為了保證正常的,有些渲染性能優(yōu)化還是有必要的。 前端性能優(yōu)化總結(jié) 資源優(yōu)化 緩存 最好的資源優(yōu)化就是不加載資源。緩存也是最見效的優(yōu)化手段。說實(shí)話,雖然說客戶端緩存發(fā)生在瀏覽器端,但緩存主要還是服務(wù)端來控制,與我們前端關(guān)系并不是很大。但還是有必要了解一下。 ...
閱讀 3322·2021-09-22 15:58
閱讀 1784·2019-08-30 14:17
閱讀 1777·2019-08-28 18:05
閱讀 1571·2019-08-26 13:33
閱讀 739·2019-08-26 12:20
閱讀 668·2019-08-26 12:18
閱讀 3260·2019-08-26 11:59
閱讀 1461·2019-08-26 10:36