JavaScript性能提升之——優(yōu)化DOM操作

在web應(yīng)用中区岗,DOM操作一直屬于是最常見的性能瓶頸厨钻,優(yōu)化DOM操作就可以大幅度提升應(yīng)用的速度,現(xiàn)今火熱的React中所使用的虛擬DOM這一賣點也是為了盡量減少DOM操作而存在的優(yōu)化方案,這一部分我們來具體說一說在DOM編程中的優(yōu)化方案。

DOM與JavaScript

通常瀏覽器會將DOM與JavaScript獨立實現(xiàn)崇呵,那么當我們訪問DOM元素的時候?qū)嶋H上是一個獨立的功能連接到另一個功能,而這個連接自然會產(chǎn)生性能消耗馅袁,每一次的訪問都會產(chǎn)生消耗的話域慷,盡可能的減少訪問次數(shù)則成為優(yōu)化的必然途徑,那么具體有哪些方法呢汗销。

減少DOM訪問次數(shù)犹褒,盡量使用JavaScript處理

舉一個簡單的例子:
<pre>
//我希望在頁面上輸出 1-100
錯誤示范:
for(var i = 1;i≤100;i++){
document.getElementById('p1').innerHTML += ' ' + i;
}
</pre>
上面這段代碼當然可以在頁面上輸出1-100的數(shù)字,但是注意弛针,這里每次循環(huán)都會調(diào)用document.getElementById來訪問DOM元素叠骑,那這里這個循環(huán)就訪問了100次DOM。
簡單修改一下:
<pre>
var strs = '';
for(var i = 1;i≤100;i++){
strs += ' ' + i;
}
document.getElementById('p1').innerHTML = strs;
</pre>
同樣的效果削茁,但是我們只訪問了一次DOM宙枷,字符串的累積操作我們完全在JavaScript中做掉房。

使用局部變量存儲DOM引用

上例子:

//我希望點擊一下按鈕數(shù)字+1
function bindActions(){
    document.getElementById('button1').onclick=function(){
    document.getElementById('p1').innerHTML = parseInt(document.getElementById('p1').innerHTML) + 1;
   //jQuery 的$('#p1').html( parseInt($('#p1').html) + 1 ) 和上面是一樣的 
  }
}
bindActions();

這個例子中慰丛,每次點擊按鈕都會讓瀏覽器重新訪問ID為P1的元素并讓其原本數(shù)字+1卓囚,要多次訪問的DOM元素,我們應(yīng)該建立局部變量保存該元素的引用璧帝,以此減少DOM的訪問捍岳。

//修改后
//我希望點擊一下按鈕數(shù)字+1
function bindActions(){
    //原生js
    var elm_button = document.getElementById('button1'),elm_textbox = document.getElementById('p1');
    elm_button .onclick=function(){
      elm_textbox .innerHTML = parseInt(elm_textbox .innerHTML) + 1;
    }

    //jQuery
    var $button = $('#button1'),$textbox = $('#p1');
    $button.on('click',function(){
       $textbox.html( parseInt( $textbox.html() ) + 1 );
    })
  }
  //js與jq的這兩個命名方式是我的個人習慣
}
bindActions();

這個優(yōu)化很簡單實在,不過我覺得很多人還是嫌麻煩在用jquery的時候繼續(xù)直接 $(selector)睬隶。锣夹。。還是要養(yǎng)成建立多次引用的局部變量好八涨薄R肌!

為HTML集合做緩存
常見的HTML集合有

  • document.getElementsByTagName
  • document.getElementsByName
  • document.getElementsByClassName
  • document.links
  • document.images
  • document.forms
    這些都是返回HTML集合的屬性恤左,HTML集合是一個類數(shù)組對象贴唇,擁有與數(shù)組類似的length屬性,也能使用數(shù)組下標來獲取元素飞袋。
    使用HTML集合的時候戳气,請盡量將其緩存,使用循環(huán)語句的時候也要將length緩存( 訪問HTML集合的length屬性比訪問數(shù)組的length屬性要慢很多 )巧鸭,舉個例子:
//bad
var elms_div = document.getElementsByTagName('div');
for(var i=0;i<elms_div.length;i++){
   elms_div[i].innerHTML= i ;
}
//good
var elms_div = document.getElementsByTagName('div');
for(var i=0,len=elms_div.length;i<len;i++){
   elms_div[i].innerHTML= i ;
}

另外一點要注意瓶您,HTML是具有實時性的,它會與文檔一直保持著聯(lián)系纲仍,意思即是你每次使用集合的時候呀袱,集合的數(shù)據(jù)都是最新的,用一段代碼解釋:

var divs = document.getElementsByTagName('div');
console.log(divs.length); // 3
document.body.appendChild(document.createElement('div'));
console.log(divs.length); //4

每次訪問集合的時候他都會重新執(zhí)行查詢DOM的操作來返回最新的集合數(shù)據(jù)郑叠,這是需要注意的夜赵。

如果要使用的HTML集合的元素很多并且要頻繁操作,可以將集合內(nèi)的元素全部復(fù)制到數(shù)組中乡革,數(shù)組的速度要比HTML集合快得多( 因為集合與文檔時刻保持連接嘛 )

標準瀏覽器的原生DOM API
現(xiàn)代瀏覽器的原生JS中已經(jīng)提供了一些速度更快的原生DOM方法寇僧,如querySelectorAll()
這個方法用起來很爽署拟,類似使用css選擇器:

var elms = document.querySelectorAll('#d1 p');

不僅方便婉宰,而且這個方法返回的不是HTML集合,而是一個類數(shù)組的對象NodeList推穷,也正因為它不是HTMl集合心包,那么它自然就不會有集合的性能問題( 實時連接 )

重繪與重排

這一個點我覺得應(yīng)該是大部分人都不會關(guān)注的,不說關(guān)注馒铃,就連知道什么是重繪和重排的人都挺少的我估計= =蟹腾。

什么是重繪
要解釋這一點痕惋,我覺得我應(yīng)該先畫個圖

在DOM樹中每個需要被顯示的節(jié)點(display:none的元素不會在渲染樹中)在渲染樹中都會有對應(yīng)的節(jié)點,CSS模型定義中娃殖,渲染樹節(jié)點被稱為" frames " 或 "boxes" 值戳,上圖基本就是瀏覽器在獲取到資源后繪制頁面的過程,可能會有點不同不過基本流程都差不多炉爆。

當DOM樹與渲染樹構(gòu)建完成之后就瀏覽器就會繪制頁面堕虹。

假如DOM產(chǎn)生了幾何變化,那么與之對應(yīng)的渲染樹中的節(jié)點部分以及受到影響的部分都會失效芬首,然后進行重新構(gòu)建渲染樹赴捞,這個就是重排的過程

重繪很好理解,在重排之后瀏覽器重新繪制被影響至失效的部分郁稍,這個過程就是重繪

重排與重繪都會對程序UI產(chǎn)生影響赦政,盡量減少重排和重繪就是接下來我們要做的事。

什么時候會重排

布局與幾何屬性變動的時候瀏覽器就需要重排耀怜,對DOM元素的增加刪除恢着、改變位置、改變尺寸财破、改變內(nèi)容掰派、瀏覽器窗口尺寸修改都會產(chǎn)生重排,產(chǎn)生重排之后重繪是必然的左痢,但是反過來碗淌,重繪的發(fā)生并不一定是因為重排。

只要不對頁面布局以及幾何屬性修改就不需要重排抖锥,比如修改顏色只會發(fā)生重繪而并不需要重排(滾動條滾動會產(chǎn)生重排的哦)。

有興趣的同學可以去用下Google的SpeedTracer來觀測頁面渲染過程

上面說了那么多的重繪和重排碎罚,其實就是為了說明在使用JS的時候改變DOM會有什么影響磅废,如此看來,那么優(yōu)化方案的中心點其實與前面說的很類似荆烈,減少DOM操作是關(guān)鍵

合并對DOM元素的修改操作可以優(yōu)化拯勉,其次,減少重排還有下面幾種:

  • 將元素先隱藏(display:none)憔购,更新完畢后再顯示出來宫峦,舉個例子:
常見的列表按條件排序、批量增加或刪除縱列玫鸟,先將其隱藏再排序結(jié)束后再放出导绷,會比直接操縱列表省去更多的重排
  • 使用createDocumentFragment() 來更新節(jié)點。這個方法比較少人用屎飘,createDocumentFragment是一個document對象妥曲。它像是一個節(jié)點贾费,我們可以朝里面添加子節(jié)點,然后使用appendChild(fragment)將其加入目標上檐盟,被添加進去的會是fragment的子節(jié)點褂萧,并且添加到目標對象的過程中只會觸發(fā)一次重排且只訪問一次實時DOM。

  • 為需要修改的節(jié)點做一個備份葵萎,然后操作副本导犹,操作結(jié)束后替換舊的即可。簡單概括就是先cloneNode羡忘,然后修改clone的谎痢,最后replaceChildj即可。

注意DOM動畫

利用元素制作一些動畫非常常見壳坪,這里要提到的是舶得,在做動畫的時候要使用absolute,脫離了文檔流之后就算元素改變也只是小范圍的重排重繪爽蝴,否則處于文檔流中變化的話沐批,會產(chǎn)生大面積的多次重排重繪動作。

事件委托
使用事件委托來減少監(jiān)聽處理器的數(shù)量是非常有必要的蝎亚,大量的事件綁定會讓瀏覽器花費大量的資源來跟蹤事件處理器九孩。
父元素可以通過冒泡接收到其下所有元素的事件消息,通過這個特性发框,我們可以將多數(shù)的事件處理器綁定在父元素上躺彬,通過篩選是否需要觸發(fā)的元素來觸發(fā)事件。
舉個例子:

//我想點擊頁面所有a標簽彈出hello
//這里直接用jQ演示梅惯。宪拥。

//bad
$('a').on('click',function(){
  alert('hello')
});

//good
$(document.body).delegate('a','click',function(){
  alert('hello')
})

上述代碼中兩者都實現(xiàn)了點擊a標簽彈出hello的功能,但是代碼二只監(jiān)聽了body就達到了這個效果铣减,而代碼一則給每個a標簽都綁定了事件監(jiān)聽器她君,孰優(yōu)孰劣不言而喻。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末葫哗,一起剝皮案震驚了整個濱河市缔刹,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌劣针,老刑警劉巖校镐,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異捺典,居然都是意外死亡鸟廓,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來肝箱,“玉大人哄褒,你說我怎么就攤上這事』驼牛” “怎么了呐赡?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長骏融。 經(jīng)常有香客問我链嘀,道長,這世上最難降的妖魔是什么档玻? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任怀泊,我火速辦了婚禮,結(jié)果婚禮上误趴,老公的妹妹穿的比我還像新娘霹琼。我一直安慰自己,他們只是感情好凉当,可當我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布枣申。 她就那樣靜靜地躺著,像睡著了一般看杭。 火紅的嫁衣襯著肌膚如雪忠藤。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天楼雹,我揣著相機與錄音模孩,去河邊找鬼。 笑死贮缅,一個胖子當著我的面吹牛榨咐,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播谴供,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼祭芦,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了憔鬼?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤胃夏,失蹤者是張志新(化名)和其女友劉穎轴或,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體仰禀,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡照雁,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片饺蚊。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡萍诱,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出污呼,到底是詐尸還是另有隱情裕坊,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布燕酷,位于F島的核電站籍凝,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏苗缩。R本人自食惡果不足惜饵蒂,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望酱讶。 院中可真熱鬧退盯,春花似錦、人聲如沸泻肯。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽软免。三九已至宫纬,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間膏萧,已是汗流浹背漓骚。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留榛泛,地道東北人蝌蹂。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像曹锨,于是被迫代替她去往敵國和親孤个。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容