日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問(wèn)題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營(yíng)銷(xiāo)解決方案
前端性能優(yōu)化之重排和重繪

前言,最近利用碎片時(shí)間拜讀了一下尼古拉斯的另一巨作《高性能JavaScript》,今天寫(xiě)的文章從“老生常談”的頁(yè)面重繪和重排入手,去探究這兩個(gè)概念在頁(yè)面性能提升上的作用。

成都創(chuàng)新互聯(lián)公司是一家朝氣蓬勃的網(wǎng)站建設(shè)公司。公司專注于為企業(yè)提供信息化建設(shè)解決方案。從事網(wǎng)站開(kāi)發(fā),網(wǎng)站制作,網(wǎng)站設(shè)計(jì),網(wǎng)站模板,微信公眾號(hào)開(kāi)發(fā),軟件開(kāi)發(fā),小程序開(kāi)發(fā),10余年建站對(duì)履帶攪拌車(chē)等多個(gè)行業(yè),擁有多年設(shè)計(jì)經(jīng)驗(yàn)。

一.重排 & 重繪

有經(jīng)驗(yàn)的大佬對(duì)這個(gè)概念一定不會(huì)陌生,“瀏覽器輸入U(xiǎn)RL發(fā)生了什么”。估計(jì)大家已經(jīng)爛熟于心了,從計(jì)算機(jī)網(wǎng)絡(luò)到JS引擎,一路飛奔到瀏覽器渲染引擎。 經(jīng)驗(yàn)越多就能理解的越深。感興趣的同學(xué)可以看一下這篇文章,深度和廣度俱佳 從輸入 URL 到頁(yè)面加載的過(guò)程?如何由一道題完善自己的前端知識(shí)體系!

切回正題,我們繼續(xù)探討何為重排。瀏覽器下載完頁(yè)面所有的資源后,就要開(kāi)始構(gòu)建DOM樹(shù),于此同時(shí)還會(huì)構(gòu)建渲染樹(shù)(Render Tree)。(其實(shí)在構(gòu)建渲染樹(shù)之前,和DOM樹(shù)同期會(huì)構(gòu)建Style Tree。DOM樹(shù)與Style Tree合并為渲染樹(shù))

  • DOM樹(shù)

表示頁(yè)面的結(jié)構(gòu)

  • 渲染樹(shù)

表示頁(yè)面的節(jié)點(diǎn)如何顯示

一旦渲染樹(shù)構(gòu)建完成,就要開(kāi)始繪制(paint)頁(yè)面元素了。當(dāng)DOM的變化引發(fā)了元素幾何屬性的變化,比如改變?cè)氐膶捀?,元素的位置,?dǎo)致瀏覽器不得不重新計(jì)算元素的幾何屬性,并重新構(gòu)建渲染樹(shù),這個(gè)過(guò)程稱為“重排”。完成重排后,要將重新構(gòu)建的渲染樹(shù)渲染到屏幕上,這個(gè)過(guò)程就是“重繪”。簡(jiǎn)單的說(shuō),重排負(fù)責(zé)元素的幾何屬性更新,重繪負(fù)責(zé)元素的樣式更新。而且,重排必然帶來(lái)重繪,但是重繪未必帶來(lái)重排。比如,改變某個(gè)元素的背景,這個(gè)就不涉及元素的幾何屬性,所以只發(fā)生重繪。

二. 重排觸發(fā)機(jī)制

上面已經(jīng)提到了,重排發(fā)生的根本原理就是元素的幾何屬性發(fā)生了改變,那么我們就從能夠改變?cè)貛缀螌傩缘慕嵌热胧?/p>

  • 添加或刪除可見(jiàn)的DOM元素
  • 元素位置改變
  • 元素本身的尺寸發(fā)生改變
  • 內(nèi)容改變
  • 頁(yè)面渲染器初始化
  • 瀏覽器窗口大小發(fā)生改變

三. 如何進(jìn)行性能優(yōu)化

重繪和重排的開(kāi)銷(xiāo)是非常昂貴的,如果我們不停的在改變頁(yè)面的布局,就會(huì)造成瀏覽器耗費(fèi)大量的開(kāi)銷(xiāo)在進(jìn)行頁(yè)面的計(jì)算,這樣的話,我們頁(yè)面在用戶使用起來(lái),就會(huì)出現(xiàn)明顯的卡頓?,F(xiàn)在的瀏覽器其實(shí)已經(jīng)對(duì)重排進(jìn)行了優(yōu)化,比如如下代碼:

 
 
 
 
  1. var div = document.querySelector('.div'); 
  2. div.style.width = '200px'; 
  3. div.style.background = 'red'; 
  4. div.style.height = '300px';

比較久遠(yuǎn)的瀏覽器,這段代碼會(huì)觸發(fā)頁(yè)面2次重排,在分別設(shè)置寬高的時(shí)候,觸發(fā)2次,當(dāng)代的瀏覽器對(duì)此進(jìn)行了優(yōu)化,這種思路類似于現(xiàn)在流行的MVVM框架使用的虛擬DOM,對(duì)改變的DOM節(jié)點(diǎn)進(jìn)行依賴收集,確認(rèn)沒(méi)有改變的節(jié)點(diǎn),就進(jìn)行一次更新。但是瀏覽器針對(duì)重排的優(yōu)化雖然思路和虛擬DOM接近,但是還是有本質(zhì)的區(qū)別。大多數(shù)瀏覽器通過(guò)隊(duì)列化修改并批量執(zhí)行來(lái)優(yōu)化重排過(guò)程。也就是說(shuō)上面那段代碼其實(shí)在現(xiàn)在的瀏覽器優(yōu)化下,只構(gòu)成一次重排。

但是還是有一些特殊的元素幾何屬性會(huì)造成這種優(yōu)化失效。比如:

  • offsetTop, offsetLeft,...
  • scrollTop, scrollLeft, ...
  • clientTop, clientLeft, ...
  • getComputedStyle() (currentStyle in IE)

為什么造成優(yōu)化失效呢?仔細(xì)看這些屬性,都是需要實(shí)時(shí)回饋給用戶的幾何屬性或者是布局屬性,當(dāng)然不能再依靠瀏覽器的優(yōu)化,因此瀏覽器不得不立即執(zhí)行渲染隊(duì)列中的“待處理變化”,并隨之觸發(fā)重排返回正確的值。

接下來(lái)深入的介紹幾種性能優(yōu)化的小TIPS

3.1 最小化重繪和重排

既然重排&重繪是會(huì)影響頁(yè)面的性能,尤其是糟糕的JS代碼更會(huì)將重排帶來(lái)的性能問(wèn)題放大。既然如此,我們首先想到的就是減少重排重繪。

3.1.1. 改變樣式

考慮下面這個(gè)例子:

 
 
 
 
  1. // javascript 
  2. var el = document.querySelector('.el'); 
  3. el.style.borderLeft = '1px'; 
  4. el.style.borderRight = '2px'; 
  5. el.style.padding = '5px';

這個(gè)例子其實(shí)和上面那個(gè)例子是一回事兒,在最糟糕的情況下,會(huì)觸發(fā)瀏覽器三次重排。然鵝更高效的方式就是合并所有的改變一次處理。這樣就只會(huì)修改DOM節(jié)點(diǎn)一次,比如改為使用cssText屬性實(shí)現(xiàn):

 
 
 
 
  1. var el = document.querySelector('.el'); 
  2. el.style.cssText = 'border-left: 1px; border-right: 2px; padding: 5px';

沿著這個(gè)思路,聰明的老鐵一定就說(shuō)了,你直接改個(gè)類名不也妥妥的。沒(méi)錯(cuò),還有一種減少重排的方法就是切換類名,而不是使用內(nèi)聯(lián)樣式的cssText方法。使用切換類名就變成了這樣:

 
 
 
 
  1. // css 
  2. .active { 
  3. padding: 5px; 
  4. border-left: 1px; 
  5. border-right: 2px; 
  6. // javascript 
  7. var el = document.querySelector('.el'); 
  8. el.className = 'active';

3.1.2 批量修改DOM

如果我們需要對(duì)DOM元素進(jìn)行多次修改,怎么去減少重排和重繪的次數(shù)呢?有的同學(xué)又要說(shuō)了,利用上面修改樣式的方法不就行了嗎?;剡^(guò)頭看一下造成頁(yè)面重排的幾個(gè)要點(diǎn)里,可以明確的看到,造成元素幾何屬性發(fā)生改變就會(huì)觸發(fā)重排,現(xiàn)在需要增加10個(gè)節(jié)點(diǎn),必然涉及到DOM的修改,這個(gè)時(shí)候就需要利用批量修改DOM這種優(yōu)化方式了,這里也能看到,改變樣式最小化重繪和重排這種優(yōu)化方式適用于單個(gè)存在的節(jié)點(diǎn)。

批量修改DOM元素的核心思想是:

  • 讓該元素脫離文檔流
  • 對(duì)其進(jìn)行多重改變
  • 將元素帶回文檔中

打個(gè)比方,我們主機(jī)硬盤(pán)出現(xiàn)了故障,常見(jiàn)的辦法就是把硬盤(pán)卸下來(lái),用專業(yè)的工具測(cè)試哪里有問(wèn)題,待修復(fù)后再安裝上去。要是直接在主板上面用螺絲刀弄來(lái)弄去,估計(jì)主板一會(huì)兒也要壞了...

這個(gè)過(guò)程引發(fā)倆次重排,***步和第三步,如果沒(méi)有這兩步,可以想象一下,第二步每次對(duì)DOM的增刪都會(huì)引發(fā)一次重排。那么知道批量修改DOM的核心思想后,我們?cè)倭私馊N可以使元素可以脫離文檔流的方法,注意,這里不使用css中的浮動(dòng)&絕對(duì)定位,這是風(fēng)馬牛不相及的概念。

  • 隱藏元素,進(jìn)行修改后,然后再顯示該元素
  • 使用文檔片段創(chuàng)建一個(gè)子樹(shù),然后再拷貝到文檔中
  • 將原始元素拷貝到一個(gè)獨(dú)立的節(jié)點(diǎn)中,操作這個(gè)節(jié)點(diǎn),然后覆蓋原始元素

看一下下面這個(gè)代碼示例:

 
 
 
 
  1. // html 
  2. xiaomi
  3. miui
  4. // javascript 現(xiàn)在需要添加帶有如下信息的li節(jié)點(diǎn) 
  5. let data = [ 
  6. name: 'tom', 
  7. url: 'https://www.baidu.com', 
  8. }, 
  9. name: 'ann', 
  10. url: 'https://www.techFE.com' 
  11. ]

首先,我們先寫(xiě)一個(gè)通用的用于將新數(shù)據(jù)更新到指定節(jié)點(diǎn)的方法:

 
 
 
 
  1. // javascript 
  2. function appendNode($node, data) { 
  3. var a, li; 
  4. for(let i = 0, max = data.length; i < max; i++) { 
  5. a = document.createElement('a'); 
  6. li = document.createElement('li'); 
  7. a.href = data[i].url; 
  8. a.appendChild(document.createTextNode(data[i].name)); 
  9. li.appendChild(a); 
  10. $node.appendChild(li); 
  11. }

首先我們忽視所有的重排因素,大家肯定會(huì)這么寫(xiě):

 
 
 
 
  1. let ul = document.querySelector('#mylist'); 
  2. appendNode(ul, data);

使用這種方法,在沒(méi)有任何優(yōu)化的情況下,每次插入新的節(jié)點(diǎn)都會(huì)造成一次重排(這幾部分我們都先討論重排,因?yàn)橹嘏攀切阅軆?yōu)化的***步)??紤]這個(gè)場(chǎng)景,如果我們添加的節(jié)點(diǎn)數(shù)量眾多,而且布局復(fù)雜,樣式復(fù)雜,那么能想到的是你的頁(yè)面一定非常卡頓。我們利用批量修改DOM的優(yōu)化手段來(lái)進(jìn)行重構(gòu)

1)隱藏元素,進(jìn)行修改后,然后再顯示該元素

 
 
 
 
  1. let ul = document.querySelector('#mylist'); 
  2. ul.style.display = 'none'; 
  3. appendNode(ul, data); 
  4. ul.style.display = 'block';

這種方法造成倆次重排,分別是控制元素的顯示與隱藏。對(duì)于復(fù)雜的,數(shù)量巨大的節(jié)點(diǎn)段落可以考慮這種方法。為啥使用display屬性呢,因?yàn)閐isplay為none的時(shí)候,元素就不在文檔流了,還不熟悉的老鐵,手動(dòng)Google一下,display:none, opacity: 0, visibility: hidden的區(qū)別

2)使用文檔片段創(chuàng)建一個(gè)子樹(shù),然后再拷貝到文檔中

 
 
 
 
  1. let fragment = document.createDocumentFragment(); 
  2. appendNode(fragment, data); 
  3. ul.appendChild(fragment);

我是比較喜歡這種方法的,文檔片段是一個(gè)輕量級(jí)的document對(duì)象,它設(shè)計(jì)的目的就是用于更新,移動(dòng)節(jié)點(diǎn)之類的任務(wù),而且文檔片段還有一個(gè)好處就是,當(dāng)向一個(gè)節(jié)點(diǎn)添加文檔片段時(shí),添加的是文檔片段的子節(jié)點(diǎn)群,自身不會(huì)被添加進(jìn)去。不同于***種方法,這個(gè)方法并不會(huì)使元素短暫消失造成邏輯問(wèn)題。上面這個(gè)例子,只在添加文檔片段的時(shí)候涉及到了一次重排。

3)將原始元素拷貝到一個(gè)獨(dú)立的節(jié)點(diǎn)中,操作這個(gè)節(jié)點(diǎn),然后覆蓋原始元素

 
 
 
 
  1. let old = document.querySelector('#mylist'); 
  2. let clone = old.cloneNode(true); 
  3. appendNode(clone, data); 
  4. old.parentNode.replaceChild(clone, old);

可以看到這種方法也是只有一次重排。總的來(lái)說(shuō),使用文檔片段,可以操作更少的DOM(對(duì)比使用克隆節(jié)點(diǎn)),最小化重排重繪次數(shù)。

3.1.3 緩存布局信息

緩存布局信息這個(gè)概念,在《高性能JavaScript》DOM性能優(yōu)化中,多次提到類似的思想,比如我現(xiàn)在要得到頁(yè)面ul節(jié)點(diǎn)下面的100個(gè)li節(jié)點(diǎn),***的辦法就是***次獲取后就保存起來(lái),減少DOM的訪問(wèn)以提升性能,緩存布局信息也是同樣的概念。前面有講到,當(dāng)訪問(wèn)諸如offsetLeft,clientTop這種屬性時(shí),會(huì)沖破瀏覽器自有的優(yōu)化————通過(guò)隊(duì)列化修改和批量運(yùn)行的方法,減少重排/重繪版次。所以我們應(yīng)該盡量減少對(duì)布局信息的查詢次數(shù),查詢時(shí),將其賦值給局部變量,使用局部變量參與計(jì)算。

看以下樣例:

將元素div向右下方平移,每次移動(dòng)1px,起始位置100px, 100px。性能糟糕的代碼:

 
 
 
 
  1. div.style.left = 1 + div.offsetLeft + 'px'; 
  2. div.style.top = 1 + div.offsetTop + 'px';

這樣造成的問(wèn)題就是,每次都會(huì)訪問(wèn)div的offsetLeft,造成瀏覽器強(qiáng)制刷新渲染隊(duì)列以獲取***的offsetLeft值。更好的辦法就是,將這個(gè)值保存下來(lái),避免重復(fù)取值

 
 
 
 
  1. current = div.offsetLeft; 
  2. div.style.left = 1 + ++current + 'px'; 
  3. div.style.top = 1 + ++current + 'px'; 

文章標(biāo)題:前端性能優(yōu)化之重排和重繪
轉(zhuǎn)載注明:http://m.5511xx.com/article/coeggdo.html