本身JS操作DOM就比較消耗性能,你可以理解爲JS和dom是獨立的小島,用橋實現兩者的聯繫,但橋很窄,要過路費,所以我們要盡最大可能減少過橋的次數。
再加上每次操作DOM都會觸發佈局的改變、DOM樹的修改和渲染。也就是說操作DOM會引發重排(迴流)與重繪,這個過程也是非常消耗資源的。所以要儘量避免重複操作DOM元素。
DOM性能優化的本質:
就是減少對DOM查詢及減少DOM操作(增、刪、改)引起的重排(迴流)和重繪的次數
DOM性能優化方法:
- 合併多次對css樣式的修改,改爲一次處理
- 對DOM查詢做緩存
- 將頻繁DOM操作改爲一次性操作
- 操作DOM前,先把DOM節點刪除或隱藏
- 採用事件代理處理事件
在講解5種優化方法之前,我們需要先了解什麼是重排(迴流)和重繪,要了解重排(迴流)和重繪,就需要先了瀏覽器的渲染機制,所以我們先從瀏覽器的渲染機制開始講起。
一、瀏覽器的渲染機制
瀏覽器的整個渲染過程(下圖)
- 解析 HTML,構建 DOM 樹
- 解析 CSS,生成 CSS 規則樹
- 合併 DOM 樹和 CSS 規則樹,生成 render(渲染)樹。
- 佈局 render 樹(迴流 / 重排),負責各元素尺寸、位置的計算。
- 繪製 render 樹(painting 重繪),繪製頁面像素信息
- 瀏覽器會將各層的信息發送給 GPU,GPU 會將各層合成(composite),顯示在屏幕上。
構建渲染樹時,瀏覽器主要完成以下工作
- 從 DOM 樹的根節點開始遍歷每個可見節點
- 對每個可見節點,找到 CSS 規則樹中對應的規則,並應用它們
- 根據每個可見節點以及其對應的樣式,組合生成渲染樹
不可見節點(也就是不會出現在渲染樹中的節點)
- 一些不會渲染輸出的節點(如:script、meta、link 等)
- 一些通過 css 進行隱藏的節點(如:display: none)
注意點:
- 樣式爲display:none;的節點會在DOM樹中而不在渲染樹中
- visiblity 和 opacity 隱藏的節點在DOM和渲染樹中同時存在。
- 瀏覽器繪製之後便開始解析js文件,根據js對DOM的操作來確定是否會再次發生重排(迴流)和重繪。
二、什麼是重排(迴流)和 重繪
重排(迴流)
當渲染樹(render tree)中的一部分或全部因爲元素的規模尺寸、大小等改變時,瀏覽器需要重新計算元素在設備視口(viewport)內的確切位置和大小,需要重新佈局render樹,這個過程爲迴流(重排)。
重繪
當頁面元素樣式改變(如 color、background-color、visibility),但不影響元素在文檔流中的的位置時,瀏覽器只需將新樣式賦予元素並進行重新繪製render樹操作,這個過程爲重繪。
迴流必將引起重繪,但重繪不一定會引起迴流
什麼情況會發生重排(迴流)
- 添加或刪除可見的 DOM 元素
- 元素的位置發生變化
- 元素的尺寸發生變化(外邊距、內邊距、邊框大小、高度和寬度等)
- 內容發生變化,(比如文本變化或圖片被尺寸大小發生變化)
- 頁面渲染初始化(必然要首次重排)
- 瀏覽器的窗口 resize 尺寸變化(因爲迴流是根據視口的大小來計算元素的位置和大小的)
- 獲取元素位置和大小相關的屬性和方法,因爲都需要返回最新的佈局信息,因此瀏覽器不得不觸發迴流重繪來返回正確的值
- offset(Top / Left / Width / Height)
- scroll(Top / Left / Width / Height)
- client(Top / Left / Width / Height)
- width、height
- 調用了 getComputeStyle() 或者 IE 的 currentStyle
三、DOM性能優化的4種方法
① 合併多次對css樣式的修改,改爲一次處理
優化前樣式代碼
oLi.style.width = "100px";
oLi.style.height = "20px";
oLi.style.background = "pink";方法一:用cssText一次性處理
oLi.style.cssText = "width:100px;height:20px;background:pink";
方法二:用className一次性處理
.item{width:100px;height:20px; padding: 0px; border: 0px; color: rgb(197, 134, 192); --tt-darkmode-color: #C586C0;">pink;} /*定義類樣式*/
oLi.className='item' //js添加類樣式
三種情況下,消耗時間對比
方法一:優化前方法二:cssText處理後方法三:className處理後370.9970703125 ms243.667236328125 ms147.678955078125 ms
<body>
<ul id="list"></ul>
<script>
console.time("優化前"); //測試執行時間代碼
var oUl = document.getElementById("list");
for (var i = 0; i < 50000; i++) {
var oLi = document.createElement("li");
//方法一
oLi.style.width = "100px";
oLi.style.height = "20px";
oLi.style.background = "pink";
//方法二:
// oLi.style.cssText = "width:100px;height:20px;background:pink";
//方法三:
/* oLi.className = "item";
oUl.appendChild(oLi);*/
}
console.timeEnd("優化前"); //測試執行時間代碼
</script>
</body>
② 對DOM查詢做緩存
不要頻繁的去查詢DOM,把查詢到的內容保存在變量中,後面直接通過變量來操作就好。
未優化前,要頻繁的查詢DOM
for (var i = 0; i < document.querySelectorAll("li").length; i++) {....... }
優化後,只需要查詢一次DOM
var n = document.querySelectorAll("li").length;
for (var i = 0; i < n; i++) {......}
優化後與優化前耗時對比未優化前耗時優化後耗時206.722900390625 ms154.436767578125 ms<script>
window.onload = function () {
//優化前代碼
console.time("時間記錄");//測試執行時間代碼
for (var i = 0; i < document.querySelectorAll("li").length; i++) {
console.log(i);
}
/* 優化後代碼
var n = document.querySelectorAll("li").length;
for (var i = 0; i < n; i++) {
console.log(i);
} */
console.timeEnd("時間記錄"); //測試執行時間代碼
};
</script>
<body>
<ul>
<li></li>
<!--以下重複,菜5000個li-->
</ul>
</body>
③、將頻繁DOM操作改爲一次性操作
這裏面要理解一個概念DOM文檔片段(虛擬節點對象)文檔片段的作用是充當其它要被添加到文檔的節點的倉庫 。他自己永遠不會被添加到文檔樹中,但是他能包含和操作節點。可以通過
document.createDocumentFragment()方法來創建文檔片段。
具體的代碼實現
<body>
<ul id="list"></ul>
<script>
console.time("優化後");
const oUl = document.getElementById("list");
//創建一個文檔片段,些時還沒有插入到DOM中,存在內存中
const frag = document.createDocumentFragment();
//執行行入操作
for (let x = 0; x < 10000; x++) {
const li = document.createComment("li");
//將DOM先放到文檔片段中,這樣不會頻繁操作DOM
frag.appendChild(li);
}
//最後一次性將10000個li插入到DOM樹中
oUl.appendChild(frag);
console.timeEnd("優化後");
</script>
</body>
這個優化消耗的時間需要在正常的網頁上去測試才能看到效果,因爲如果頁面中沒有其它元素,也就不會造成重排和重繪,那對於時間上的消耗也是看不到的。大家可以自己行測試。
④ 操作DOM前,先把DOM節點刪除或隱藏
如果要對一個元素進行多次DOM操作,可以先將其隱藏,操作完成後再顯示。這樣只在隱藏和顯示時觸發2次重排,而不會是在每次進行操作時都出發一次重排。因爲display:none時的元素不在渲染樹中,因此對隱藏的元素操作不會引發其他元素的重排。
<body>
<ul id="list"></ul>
<script>
var oUl = document.getElementById("list");
oUl.style.display = "none";/*先隱藏元素*/
for (var i = 0; i < 15000; i++) {
var li = document.createElement("li");
oUl.appendChild(li);
}
oUl.style.display = "block";/*DOM操作完再顯示*/
</script>
</body>
⑤ 事件代理
事件委託,利用瀏覽器事件冒泡捕獲減少頁面事件綁定,我們可以指定一個事件處理程序就可以管理某一類型的所有事件。事件函數過多會佔用大量內存,而且綁定事件的DOM元素越多會增加訪問dom的次數,對頁面的交互就緒時間也會有延遲。
<body>
<ul id="list">
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
</ul>
<script>
//事件委託
var oUl = document.getElementById("list");
oUl.onclick = function (e) {
console.log(e.target.innerHTML);
};
/* 未用事件委託
var li = document.querySelectorAll("#list li");
var n = li.length;
for (let i = 0; i < n; i++) {
li[i].onclick = function () {
console.log(this.innerHTML);
};
} */
</script>
</body>
DOM是JS階段重要的教學內容,在星辰班已經講了40個小節的課程,更多完整JavaScript課程體系在我們的系統班裏有完整的呈現,包含了JavaScript基礎篇、重點、算法、原理、面試題、實戰案例講解!同時也爲你提供了前端高級工程師成長體系!(詳細看下圖內容)
如果需要深度學習的同學可以聯繫助理老師瞭解詳細的課程以及課程的報名方式!(不定期會推出活動,有大額優惠券推出,活動詳情聯繫助理老師瞭解即可!)
如果你纔開始學習前端,那麼可以先學習我們的三十天計劃(零基礎的同學報名系統班同學可以和老師溝通制定學習計劃,可以得到更快的成長!)
爲幫助到一部分同學不走彎路,真正達到一線互聯網大廠前端項目研發要求,首次實力寵粉,打造了《30天挑戰學習計劃》,內容如下:
HTML/HTML5,CSS/CSS3,JavaScript,真實企業項目開發,雲服務器部署上線,從入門到精通
- PC端項目開發(1個)
- 移動WebApp開發(2個)
- 多端響應式開發(1個)
共4大完整的項目開發 !一行一行代碼帶領實踐開發,實際企業開發怎麼做我們就是怎麼做。從學習一開始就進入工作狀態,省得浪費時間。
從學習一開始就同步使用 Git 進行項目代碼的版本的管理,Markdown 記錄學習筆記,包括真實大廠項目的開發標準和設計規範,命名規範,項目代碼規範,SEO優化規範
從藍湖UI設計稿 到 PC端,移動端,多端響應式開發項目開發
- 真機調試,雲服務部署上線;
- Linux環境下 的 Nginx 部署,Nginx 性能優化;
- Gzip 壓縮,HTTPS 加密協議,域名服務器備案,解析;
- 企業項目域名跳轉的終極解決方案,多網站、多系統部署;
- 使用 使用 Git 在線項目部署;
這些內容在《30天挑戰學習計劃》中每一個細節都有講到,包含視頻+圖文教程+項目資料素材等。只爲實力寵粉,真正一次掌握企業項目開發必備技能,不走彎路 !
過程中【不涉及】任何費用和利益,非誠勿擾 。
如果你沒有添加助理老師微信,可以添加下方微信,說明要參加30天挑戰學習計劃,來自頭條號!老師會邀請你進入學習,並給你發放相關資料。