摘要:思路合并所有改變?nèi)缓笠淮涡蕴幚硎褂脤傩孕薷念惷啃薷漠?dāng)你需要對元素進(jìn)行一系列操作的時候,不妨按照如下步驟使元素脫離文檔流對其應(yīng)用多重改變把元素帶回文檔中上面的這一套組合拳中,第一步和第三部分別會觸發(fā)一次重排。
前言:隨著vue,react, angular的流行,可能現(xiàn)在我們不必經(jīng)常的操作DOM,三大框架在副交互的操作中發(fā)揮著極大地優(yōu)勢。因為我們知道用腳本對DOM的操作非常昂貴,本文主要探討常規(guī)的DOM操作中你可能不知道的知識。
瀏覽器中的DOM首先,我們來了解一下什么是DOM,他為什么慢?
DOM,天生就慢文檔對象模型 (DOM)是 W3C(萬維網(wǎng)聯(lián)盟)的標(biāo)準(zhǔn),是一個獨(dú)立于語言的,用于操作XML和HTML文檔的程序接口。在瀏覽器中,主要和HTML文檔打交道,盡管DOM是個與語言無關(guān)的API,但是它在瀏覽器中的接口卻是用JS實現(xiàn)的。但是通常瀏覽器會把DOM和JS分開來實現(xiàn),比如Chrome中的DOM實現(xiàn)為webkit中的webCore,但js引擎是Google自己研發(fā)的V8。既然分開了,一旦兩者需要產(chǎn)生連接,就要付出代價。
DOM 訪問與修改前面說到JS訪問DOM會產(chǎn)生而性能上的消耗,如果去修改DOM元素則代價更為昂貴,因為它會導(dǎo)致瀏覽器重新計算頁面的幾何變化。 來看兩端段代碼:
第一段代碼定義了一個普通的變量,進(jìn)行循環(huán)累加,執(zhí)行事件幾乎為。第二段代碼,每次循環(huán)會訪問兩次特定的元素:第一次讀取這個元素的innerHTML屬性,第二次重寫它。可以看到它的執(zhí)行時間將近2.8s,這段腳本會一直阻塞后續(xù)的執(zhí)行。
看清楚了這一點(diǎn),不難得到一個效率更高的版本:
用一個局部變量包層每次更新后的內(nèi)容,等待循環(huán)結(jié)束后,一次性的寫入頁面(盡可能的把更多的工作交給js的部分來做)??梢钥吹娇炝私?000倍。
顯而易見。訪問DOM的次數(shù)越多,代碼的運(yùn)行速度越慢,因此,通用的經(jīng)驗做法是:減少訪問DOM的次數(shù),把運(yùn)算盡量留給JS處理。這也是Vue或者React為什么要設(shè)計virtual dom 的初衷之一:把負(fù)責(zé)的DOM模型映射成JS對象,不去直接操作DOM而是通過操作JS對象,最后再進(jìn)行 innerHtml 或者 append。
我們先來看一段代碼:
var alldivs = document.getElementsByTagName("div"); for (var i = 0; i < alldivs.length; i++){ document.body.appendChild(document.createElement("div")); }
乍一看,這段代碼只是單純的把頁面中的div數(shù)量翻倍:遍歷所有的div,每次創(chuàng)建一個新的div并創(chuàng)建到添加到body中。
但事實上,這是一個死循環(huán):因為循環(huán)的退出條件alldivs.length在每一次循環(huán)結(jié)束后都會增加,因為這個HTML元素集合反映的是底層文檔元素的實時狀態(tài)。 類似于下面的HTML集合其實都會存在這樣的問題:
document.getElementsByTagName()
document.getElementsByClassName()
document.getElementsByName()
....
其次上面的代碼也會存在一個性能問題,就是每次都需要訪問DOM對象的length屬性。其實我們也可以用一個變量緩存起來對象的length屬性:
var len = document.getElementsByTagName("div").length; for (var i = 0; i < len; i++){ document.body.appendChild(document.createElement("div")); }重繪與重排
瀏覽器用來顯示頁面的所有“組件”,有:HTML標(biāo)簽、js、css、圖片——之后會解析并生成兩個內(nèi)部的數(shù)據(jù)結(jié)構(gòu):
DOM樹(表示頁面結(jié)構(gòu))
渲染樹(表示DOM節(jié)點(diǎn)應(yīng)該如何表示)
DOM樹中的每一個需要顯示的節(jié)點(diǎn)在渲染樹中至少存在一個對應(yīng)的節(jié)點(diǎn)。
渲染樹中的節(jié)點(diǎn)被稱為“幀(frames)”或“盒(boxes)”,符合css盒模型的定義,理解頁面元素為一個具有padding、margin、borders和position的盒子。
一旦渲染樹構(gòu)建完成,瀏覽器就開始顯示頁面元素,這個過程稱為繪制(paint)。
當(dāng)DOM的變化影響了元素的幾何屬性(寬、高)——比如改變改變了邊框的寬度或者給一個段落增加一些文字導(dǎo)致其行數(shù)的增加——瀏覽器就需要重新計算元素的幾何屬性,同樣,頁面中其他元素的幾何屬性和位置也會因此受到影響。
瀏覽器會使渲染樹中收到影響的部分消失,重新構(gòu)建渲染樹,這個過程稱為“重排(reflow)”。重排完成之后,瀏覽器會重新將受到影響的部分繪制到瀏覽器中,這個過程稱之為“重繪(repaint)”。
如果改變的不是元素的幾何屬性,如:改變元素的背景顏色,不會發(fā)生重排,只會發(fā)生一次重繪,因為元素的布局并沒有改變。
不管是重繪還是重排,都是代價昂貴的操作,它們會導(dǎo)致web應(yīng)用程序的UI反應(yīng)遲鈍,應(yīng)當(dāng)盡可能的減少這類過程的發(fā)生。
重排何時發(fā)生?
添加或刪除可見的DOM元素
元素位置的改變
元素尺寸的改變(padding、margin、border、height、width)
內(nèi)容改變(文本改變或圖片尺寸改變)
頁面渲染器初始化
瀏覽器窗口尺寸改變
滾動條的出現(xiàn)(會觸發(fā)整個頁面的重排)
改變樣式
一個栗子:
var el = document.getElementById("mydiv"); el.style.borderLeft = "1px"; el.style.borderRight = "2px"; el.style.padding = "5px";
示例中,元素的三個樣式被改變,而且每一個都會影響元素的幾何結(jié)構(gòu)。在最糟糕的情況下,這段代碼會觸發(fā)三次重排(大部分現(xiàn)代瀏覽器為此做了優(yōu)化,只會觸發(fā)一次重排)。從另一個角度看,這段代碼四次訪問DOM,可以被優(yōu)化。
var el = document.getElementById("mydiv"); //思路:合并所有改變?nèi)缓笠淮涡蕴幚? //method_1:使用cssText屬性 el.style.cssText = "border-left: 1px; border-right: 2px; padding: 5px"; //method_2:修改類名 el.className = "anotherClass";
當(dāng)你需要對DOM元素進(jìn)行一系列操作的時候,不妨按照如下步驟:
使元素脫離文檔流
對其應(yīng)用多重改變
把元素帶回文檔中
上面的這一套組合拳中,第一步和第三部分別會觸發(fā)一次重排。但是如果你忽略了這兩個步驟,那么在第二步所產(chǎn)生的任何修改都會觸發(fā)一次重排。
在此安利三種可以使DOM元素脫離文檔流的方法:
隱藏元素
使用文檔片段(document fragment)在當(dāng)前DOM之外構(gòu)建一個子樹,再把它拷貝回文檔
將原始元素拷貝到一個脫離文檔的節(jié)點(diǎn)中,修改副本,完成后再替換原始元素
一般情況下,重排只影響渲染樹中的一小部分,但也可能影響很大的一部分,甚至是整個渲染樹。
瀏覽器所需的重排次數(shù)越少,應(yīng)用程序的響應(yīng)速度也就越快。
想象這樣一種情況,頁面的底部有一個動畫,會推移頁面整個余下的部分,這將是一次代價昂貴的大規(guī)模重排!用戶也勢必會感覺到頁面一卡一卡的。
因此,使用以下步驟可以避免頁面中的大部分重排:
使用絕對定位讓頁面上的動畫元素脫離文檔流
動畫展示階段
動畫結(jié)束時,將元素恢復(fù)定位。
從IE7開始,IE允許在任何元素上使用:hover這個css選擇器。
然而,如果你有大量元素使用了:hover,你會發(fā)現(xiàn),賊喇慢!
關(guān)于
作者:monkeyWang
本人主頁:https://monkeywangs.github.io/
微信公眾號:會不定期推送前端技術(shù)文章,歡迎關(guān)注
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/89540.html
摘要:它大致概述并討論了前端工程的實踐如何學(xué)習(xí)它,以及在年實踐時使用什么工具。目的是每年發(fā)布一次內(nèi)容更新。前端實踐第一部分廣泛描述了前端工程的實踐。對大多數(shù)人來說,函數(shù)式編程看起來更加自然。 1 Front-End Developer Handbook 2017 地址:https://frontendmasters.com/b... 這是任何人都可以用來了解前端開發(fā)實踐的指南。它大致概述并...
摘要:它大致概述并討論了前端工程的實踐如何學(xué)習(xí)它,以及在年實踐時使用什么工具。目的是每年發(fā)布一次內(nèi)容更新。前端實踐第一部分廣泛描述了前端工程的實踐。對大多數(shù)人來說,函數(shù)式編程看起來更加自然。 1 Front-End Developer Handbook 2017 地址:https://frontendmasters.com/b... 這是任何人都可以用來了解前端開發(fā)實踐的指南。它大致概述并...
摘要:它大致概述并討論了前端工程的實踐如何學(xué)習(xí)它,以及在年實踐時使用什么工具。目的是每年發(fā)布一次內(nèi)容更新。前端實踐第一部分廣泛描述了前端工程的實踐。對大多數(shù)人來說,函數(shù)式編程看起來更加自然。 1 Front-End Developer Handbook 2017 地址:https://frontendmasters.com/b... 這是任何人都可以用來了解前端開發(fā)實踐的指南。它大致概述并...
閱讀 2687·2021-11-16 11:53
閱讀 2750·2021-07-26 23:38
閱讀 2081·2019-08-30 15:55
閱讀 1763·2019-08-30 13:21
閱讀 3686·2019-08-29 17:26
閱讀 3316·2019-08-29 13:20
閱讀 884·2019-08-29 12:20
閱讀 3203·2019-08-26 10:21