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