[TOC]
>摘抄自[https://www.cnblogs.com/dujingjie/p/5784890.html](https://www.cnblogs.com/dujingjie/p/5784890.html)
>[https://www.cnblogs.com/zhutao/p/6551216.html](https://www.cnblogs.com/zhutao/p/6551216.html)
## 概念
首先我們要明確頁面在文檔加載完成之后到完全顯示中間的過程是
1. 根據文檔生成`DOM樹`(包括display:none的節點)
2. 在DOM樹基礎上根據節點的幾何屬性(margin/padding/width/height等)生成`render樹`(不包括display:none、head節點但會包含visibility:hidden節點)
3. 在render樹基礎上進行進一步渲染包括color,outline等樣式
### 回流
`reflow`:當render樹中的一部分或者全部因為大小邊距等問題發生改變而需要重建的過程叫做**回流**
當render tree中的一部分(或全部)因為元素的規模尺寸,布局,隱藏等改變而需要重新構建。這就稱為回流(reflow)。每個頁面至少需要一次回流,就是在頁面第一次加載的時候。在回流的時候,瀏覽器會使渲染樹中受到影響的**部分**失效,并重新構造**這部分**渲染樹,完成回流后,瀏覽器會重新繪制受影響的部分到屏幕中,該過程成為重繪。
reflow:例如某個子元素樣式發生改變,直接影響到了其父元素以及往上追溯很多祖先元素(包括兄弟元素),這個時候瀏覽器要重新去渲染這個子元素**相關聯的**所有元素的過程稱為回流。
### 重繪
`repaint`:當元素的一部分屬性發生變化,如外觀背景色不會引起布局變化而需要重新渲染的過程叫做**重繪**
## 什么會引起回流
這個問題其實很簡單,什么引起的,看定義不就可以了?
問題是引起的原因可能會很多
籠統來說當頁面的布局和幾何屬性發生變化的時候就會引起回流。具體來說大概分別5大類:
1. 首當其沖自然是dom樹結構變化,比如你刪除或者添加某個node.
2. 元素幾何屬性變化,包括margin,padding,height,width,border等
3. 頁面渲染初始化
4. 獲取某些屬性。雖然瀏覽器引擎可能會針對重排做了優化,比如Opera,它會等到有足夠 數量的變化發生,或者等到一定的時間,或者等一個線程結束,再一起處理,這樣就只發生一次重排。但除了render樹的直接變化,當獲取一些屬性時,瀏覽器為取得 正確的值也會觸發回流。這樣就使得瀏覽器的優化失效了。這些屬性包括:offsetTop、offsetLeft、 offsetWidth、offsetHeight、scrollTop、scrollLeft、scrollWidth、scrollHeight、 clientTop、clientLeft、clientWidth、clientHeight、getComputedStyle() (currentStyle in IE)。所以,在多次使用這些值時應進行緩存。(這段我是直接引用的。。。)
5. 瀏覽器窗口發生變化-resize事件發生時
以上,其實理解起來很容易。所謂的render樹就是識別了幾何屬性的dom數,好像我們畫人體的時候,dom樹是先確定都有什么比如四肢,頭部,身體,其他器官等;而render樹則是確定這個人的高矮胖瘦,頭發是否蓋眼睛等,如果我們在繪畫過程中發現脖子長了那就慘了,脖子下面都要重畫。這就是回流了。如果發現只是手指畫的有問題也還是要回流但我們只需要重畫手指。(當然,我說的是手就是手,沒什么特別造型的時候);當我們的render樹完事了,也就是人體大概輪廓我們都畫好了,就可以上色了,**換個發色這種我們叫重繪**。
### 習題
```
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,再一次 回流+重繪
```
現在我們大概都能得出的結論是:回流比重繪的代價要高,至于具體的花銷跟render樹有多少節點需要重新構建有關。
還有就是,**回流一定會伴隨著重繪,但是重繪不一定會引起回流**。(回流并不是一定會導致**所有**元素重繪(?),比如說一次appendChild(老元素)的操作)
## 我們要如何避免
從上面的實例代碼中可以看到一共七行代碼引起了6次左右的回流、重繪(上面的代碼我大老遠從別的頁面拿過來當然不只是就用那一次,哈哈),而且我們剛剛還知道了回流花銷真是不小,那么瀏覽器是不是真的每次js語句引起了回流他就執行一下呢?恩,后面的內容我是看的參考資料的:**等隊列中的操作到了一定的數量或者到了一定的時間間隔**,瀏覽器就會flush隊列,進行一個批處理。這樣就會讓多次的回流、重繪變成一次回流重繪。
但是盡管瀏覽器挺機智地幫我們優化了代碼,我們自己作死也是沒救的,比如你去請求
1. offsetTop, offsetLeft, offsetWidth, offsetHeight
2. scrollTop/Left/Width/Height
3. clientTop/Left/Width/Height
4. width,height
5. 請求了getComputedStyle(), 或者 IE的 currentStyle
瀏覽器為了給你返回一個比較**精確**的答案,他會**提前flush**隊列,因為隊列中可能會有影響這些值的操作。
所以我們可以做的是:
1.將那些改變樣式的操作集合在一次完事,直接改變className或者cssText
2.讓要操作的元素進行離線處理,處理完事以后再一起更新
### (1)使用DocumentFragment進行緩存操作,引發一次回流和重繪
課外延伸:
DocumentFragment 節點不屬于文檔樹,繼承的 parentNode 屬性總是 null。
不過它有一種特殊的行為,該行為使得它非常有用,即當請求把一個 DocumentFragment 節點插入文檔樹時,插入的不是 DocumentFragment 自身,而是它的所有子孫節點。這使得 DocumentFragment 成了有用的占位符,暫時存放那些一次插入文檔的節點。它還有利于實現文檔的剪切、復制和粘貼操作。
其實他就是一個游離在DOM樹外面的容器,所以你在把它插入文檔節點之前,隨便給他增刪節點都不會引起回流
### (2)使用display:none,只引發兩次回流和重繪。
道理跟上面的一樣。因為display:none的元素不會出現在render樹
### (3)使用cloneNode和replaceChild技術,引發一次回流和重繪(這條其實沒太明白)
### (4)不要經常訪問會引起瀏覽器flush隊列的屬性,非要高頻訪問的話建議緩存到變量;
### (5)將需要多次重排的元素,position屬性設為absolute或fixed
這樣此元素就脫離了文檔流,它的變化不會影響到其他元素。例如有動畫效果的元素就最好設置為絕對定位;
### (6)盡量不要使用表格布局
如果沒有定寬表格一列的寬度由最寬的一列決定,那么很可能在最后一行的寬度超出之前的列寬,引起整體回流造成table可能需要多次計算才能確定好其在渲染樹中節點的屬性,通常要花3倍于同等元素的時間。
## 總結
別人這個地方都是列了一下實驗結果,鑒于我的timeline還用不明白就不獻丑了。
如有錯誤之處,歡迎指正。
最后再強調一下,以上內容參考了文章開頭列出的資料,如有雷同,那很正常。。。
### append
- 空白目錄
- window
- location
- history
- DOM
- 什么是DOM
- JS盒子模型
- 13個核心屬性
- DOM優化
- 回流與重繪
- 未整理
- 文檔碎片
- DOM映射機制
- DOM庫封裝
- 事件
- 功能組件
- table
- 圖片延遲加載
- 跑馬燈
- 回到頂部
- 選項卡
- 鼠標跟隨
- 放大鏡
- 搜索
- 多級菜單
- 拖拽
- 瀑布流
- 數據類型的核心操作原理
- 變量提升
- 閉包(scope)
- this
- 練習題
- 各種數據類型下的常用方法
- JSON
- 數組
- object
- oop
- 單例模式
- 高級單例模式
- JS中常用的內置類
- 基于面向對象創建數據值
- 原型和原型鏈
- 可枚舉和不可枚舉
- Object.create
- 繼承的六種方式
- ES6下一代js標準
- babel
- 箭頭函數
- 對象
- es6勉強筆記
- 流程控制
- switch
- Ajax
- eval和()括號表達式
- 異常信息捕獲
- 邏輯與和或以及前后自增
- JS中的異步編程思想
- 上云
- 優化技巧
- 跨域與JSONP
- 其它跨域相關問題
- console
- HTML、XHTML、XML
- jQuery
- zepto
- 方法重寫和方法重載
- 移動端
- 響應式布局開發基礎
- 項目一:創意簡歷