前端回流/重排reflow與重繪redraw

近日有一朋友去面試,遇到如下一道題目,要給一個(gè)dom元素循環(huán)添加一組dom元素,要放在循環(huán)內(nèi)還是循環(huán)外:
偽代碼如下

for ( let i = 0 ; i < 10; i ++) {
    // 循環(huán)給div append添加dom或者其他
    // 創(chuàng)建 createDocumentFragment()  append到文檔片段中
}

相信絕多數(shù)fe都知道要放在循環(huán)外

問 : 為什么?
答:因?yàn)榉旁谘h(huán)內(nèi)會(huì)循壞的頻繁的去操作dom
問:頻繁的操作dom有什么問題?
答:因?yàn)槊看味紩?huì)去頁面找這個(gè)dom,損耗性能
問:dom 有唯一的id標(biāo)識 所以并不存在這個(gè)問題?還有其他什么原因嗎?
答 : ....

很多人都僅僅知道不能頻繁的操作dom,卻不知道為什么頻繁的去操作dom不好,另外有些人會(huì)以為因?yàn)槊看味既ゲ檎襠om,影響性能,其實(shí)重點(diǎn)不在這里,面試官主要的是考察你是否知道什么是重繪與回流

在討論頁面重繪、回流之前。需要對頁面的呈現(xiàn)流程有些了解,頁面是怎么把html結(jié)合css等顯示到瀏覽器上的,可能不同的瀏覽器略微會(huì)有些不同,但基本上都是類似的。

頁面如何把html結(jié)合css等顯示到瀏覽器上

  1. 瀏覽器把獲取到的HTML代碼解析成1個(gè)DOM樹,HTML中的每個(gè)tag都是DOM樹中的1個(gè)節(jié)點(diǎn),根節(jié)點(diǎn)就是我們常用的document對象。DOM樹里包含了所有HTML標(biāo)簽,包括display:none隱藏,還有用JS動(dòng)態(tài)添加的元素等。

  2. 瀏覽器把所有樣式(用戶定義的CSS和用戶代理)解析成樣式結(jié)構(gòu)體,在解析的過程中會(huì)去掉瀏覽器不能識別的樣式,比如IE會(huì)去掉-moz開頭的樣式,而FF會(huì)去掉_開頭的樣式。

  3. DOM Tree(樹) 和樣式結(jié)構(gòu)體組合后構(gòu)建render tree, render tree類似于DOM tree,但區(qū)別很大,render tree能識別樣式,render tree中每個(gè)node都有自己的style,而且 render tree不包含隱藏的節(jié)點(diǎn) (比如display:none的節(jié)點(diǎn),還有head節(jié)點(diǎn)),因?yàn)檫@些節(jié)點(diǎn)不會(huì)用于呈現(xiàn),而且不會(huì)影響呈現(xiàn)的,所以就不會(huì)包含到 render tree中。注意 visibility:hidden隱藏的元素還是會(huì)包含到 render tree中的,因?yàn)関isibility:hidden 會(huì)影響布局(layout),會(huì)占有空間。根據(jù)CSS2的標(biāo)準(zhǔn),render tree中的每個(gè)節(jié)點(diǎn)都稱為Box (Box dimensions),理解頁面元素為一個(gè)具有填充、邊距、邊框和位置的盒子。

  4. 一旦render tree構(gòu)建完畢后,瀏覽器就可以根據(jù)render tree來繪制頁面了。

回流與重繪

  1. 當(dāng)render tree中的一部分(或全部)因?yàn)樵氐囊?guī)模尺寸,布局,隱藏等改變而需要重新構(gòu)建, 這就稱為回流(reflow)。每個(gè)頁面至少需要一次回流,就是在頁面第一次加載的時(shí)候。在回流的時(shí)候,瀏覽器會(huì)使渲染樹中受到影響的部分失效,并重新構(gòu)造這部分渲染樹,完成回流后,瀏覽器會(huì)重新繪制受影響的部分到屏幕中,該過程成為重繪。

  2. 當(dāng)render tree中的一些元素需要更新屬性,而這些屬性只是影響元素的外觀,風(fēng)格,而不會(huì)影響布局的,比如background-color。則就叫稱為重繪。

注意:回流必將引起重繪,而重繪不一定會(huì)引起回流。

回流何時(shí)發(fā)生:

當(dāng)頁面布局和幾何屬性改變時(shí)就需要回流。下述情況會(huì)發(fā)生瀏覽器回流:

1、添加或者刪除可見的DOM元素;

2、元素位置改變;

3、元素尺寸改變——邊距、填充、邊框、寬度和高度

4、內(nèi)容改變——比如文本改變或者圖片大小改變而引起的計(jì)算值寬度和高度改變;

5、頁面渲染初始化;

6、瀏覽器窗口尺寸改變——resize事件發(fā)生時(shí);

讓我們看看下面的代碼是如何影響回流和重繪的:

var s = document.body.style;
s.padding = "2px"; // 回流+重繪
s.border = "1px solid red"; // 再一次 回流+重繪
s.color = "blue"; // 重繪
s.backgroundColor = "#ccc"; // 重繪
s.fontSize = "14px"; // 再一次 回流+重繪
document.body.appendChild(document.createTextNode('abc!'));// 添加node,再一次 回流+重繪

說到這里大家都知道回流比重繪的代價(jià)要更高,回流的花銷跟render tree有多少節(jié)點(diǎn)需要重新構(gòu)建有關(guān)系,假設(shè)你直接操作body,比如在body最前面插入1個(gè)元素,會(huì)導(dǎo)致整個(gè)render tree回流,這樣代價(jià)當(dāng)然會(huì)比較高,但如果是指body后面插入1個(gè)元素,則不會(huì)影響前面元素的回流。

聰明的瀏覽器

從上個(gè)實(shí)例代碼中可以看到幾行簡單的JS代碼就引起了6次左右的回流、重繪。而且我們也知道回流的花銷也不小,如果每句JS操作都去回流重繪的話,瀏覽器可能就會(huì)受不了。所以很多瀏覽器都會(huì)優(yōu)化這些操作,瀏覽器會(huì)維護(hù)1個(gè)隊(duì)列,把所有會(huì)引起回流、重繪的操作放入這個(gè)隊(duì)列,等隊(duì)列中的操作到了一定的數(shù)量或者到了一定的時(shí)間間隔,瀏覽器就會(huì)flush隊(duì)列,進(jìn)行一個(gè)批處理。這樣就會(huì)讓多次的回流、重繪變成一次回流重繪。

雖然有了瀏覽器的優(yōu)化,但有時(shí)候我們寫的一些代碼可能會(huì)強(qiáng)制瀏覽器提前flush隊(duì)列,這樣瀏覽器的優(yōu)化可能就起不到作用了。當(dāng)你請求向?yàn)g覽器請求一些 style信息的時(shí)候,就會(huì)讓瀏覽器flush隊(duì)列,比如:

  1. offsetTop, offsetLeft, offsetWidth, offsetHeight

  2. scrollTop/Left/Width/Height

  3. clientTop/Left/Width/Height

  4. width,height

  5. 請求了getComputedStyle(), 或者 IE的 currentStyle

當(dāng)你請求上面的一些屬性的時(shí)候,瀏覽器為了給你最精確的值,需要flush隊(duì)列,因?yàn)殛?duì)列中可能會(huì)有影響到這些值的操作。即使你獲取元素的布局和樣式信息跟最近發(fā)生或改變的布局信息無關(guān),瀏覽器都會(huì)強(qiáng)行刷新渲染隊(duì)列。

如何減少回流、重繪

減少回流、重繪其實(shí)就是需要減少對render tree的操作(合并多次多DOM和樣式的修改),并減少對一些style信息的請求,盡量利用好瀏覽器的優(yōu)化策略。具體方法有:

  1. 直接改變className
// 不好的寫法
var left = 1;
var top = 1;
el.style.left = left + "px";
el.style.top = top + "px";
// 比較好的寫法
el.className += " className";
  1. 讓要操作的元素進(jìn)行”離線處理”,處理完后一起更新
  • 使用DocumentFragment進(jìn)行緩存操作,引發(fā)一次回流和重繪;
    可以參考這篇文章 了解DocumentFragment 給我們帶來的性能優(yōu)化
  • 使用display:none技術(shù),只引發(fā)兩次回流和重繪;
    前面也提到,如果影響到頁面布局就會(huì)導(dǎo)致回流 那么先把元素設(shè)置為 display:none 最后再 display:block 即可
  • 使用cloneNode(true or false) 和 replaceChild 技術(shù),引發(fā)一次回流和重繪;
  1. 不要經(jīng)常訪問會(huì)引起瀏覽器flush隊(duì)列的屬性,如果你確實(shí)要訪問,利用緩存
// 別這樣寫
for(循環(huán)) {
el.style.left = el.offsetLeft + 5 + "px";
el.style.top = el.offsetTop + 5 + "px";
}
// 這樣寫好點(diǎn)
var left = el.offsetLeft,
top = el.offsetTop,
s = el.style; 
for (循環(huán)) { 
left += 10; 
top += 10; 
s.left = left + "px"; 
s.top = top + "px"; 
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 在討論頁面重繪、回流之前。需要對頁面的呈現(xiàn)流程有些了解,頁面是怎么把html結(jié)合css等顯示到瀏覽器上的,下面的流...
    抓住時(shí)間的尾巴吧閱讀 1,003評論 0 3
  • 在討論頁面重繪、回流之前。需要對頁面的呈現(xiàn)流程有些了解,頁面是怎么把html結(jié)合css等顯示到瀏覽器上的,下面的流...
    放風(fēng)箏的小小馬閱讀 1,293評論 0 2
  • 頁面重繪和回流以及優(yōu)化 在討論頁面重繪、回流之前。需要對頁面的呈現(xiàn)流程有些了解,頁面是怎么把html結(jié)合css等顯...
    sponing閱讀 2,129評論 1 11
  • 在討論頁面重繪、回流之前。需要對頁面的呈現(xiàn)流程有些了解,頁面是怎么把html結(jié)合css等顯示到瀏覽器上的,下面的流...
    DavieKong閱讀 755評論 2 4
  • 在討論頁面重繪、回流之前。需要對頁面的呈現(xiàn)流程有些了解,頁面是怎么把html結(jié)合css等顯示到瀏覽器上的,下面的流...
    legendtx閱讀 314評論 0 1

友情鏈接更多精彩內(nèi)容