摘要:接下來深入的介紹幾種性能優(yōu)化的小最小化重繪和重排既然重排重繪是會影響頁面的性能,尤其是糟糕的代碼更會將重排帶來的性能問題放大。前面有講到,當(dāng)訪問諸如,這種屬性時(shí),會沖破瀏覽器自有的優(yōu)化通過隊(duì)列化修改和批量運(yùn)行的方法,減少重排重繪版次。
前言,最近利用碎片時(shí)間拜讀了一下尼古拉斯的另一巨作《高性能JavaScript》,今天寫的文章從“老生常談”的頁面重繪和重排入手,去探究這兩個(gè)概念在頁面性能提升上的作用。
一.重排 & 重繪有經(jīng)驗(yàn)的大佬對這個(gè)概念一定不會陌生,“瀏覽器輸入U(xiǎn)RL發(fā)生了什么”。估計(jì)大家已經(jīng)爛熟于心了,從計(jì)算機(jī)網(wǎng)絡(luò)到JS引擎,一路飛奔到瀏覽器渲染引擎。 經(jīng)驗(yàn)越多就能理解的越深。感興趣的同學(xué)可以看一下這篇文章,深度和廣度俱佳 從輸入 URL 到頁面加載的過程?如何由一道題完善自己的前端知識體系!
切回正題,我們繼續(xù)探討何為重排。瀏覽器下載完頁面所有的資源后,就要開始構(gòu)建DOM樹,于此同時(shí)還會構(gòu)建渲染樹(Render Tree)。(其實(shí)在構(gòu)建渲染樹之前,和DOM樹同期會構(gòu)建Style Tree。DOM樹與Style Tree合并為渲染樹)
DOM樹
表示頁面的結(jié)構(gòu)
渲染樹
表示頁面的節(jié)點(diǎn)如何顯示
一旦渲染樹構(gòu)建完成,就要開始繪制(paint)頁面元素了。當(dāng)DOM的變化引發(fā)了元素幾何屬性的變化,比如改變元素的寬高,元素的位置,導(dǎo)致瀏覽器不得不重新計(jì)算元素的幾何屬性,并重新構(gòu)建渲染樹,這個(gè)過程稱為“重排”。完成重排后,要將重新構(gòu)建的渲染樹渲染到屏幕上,這個(gè)過程就是“重繪”。簡單的說,重排負(fù)責(zé)元素的幾何屬性更新,重繪負(fù)責(zé)元素的樣式更新。而且,重排必然帶來重繪,但是重繪未必帶來重排。比如,改變某個(gè)元素的背景,這個(gè)就不涉及元素的幾何屬性,所以只發(fā)生重繪。
二. 重排觸發(fā)機(jī)制上面已經(jīng)提到了,重排發(fā)生的根本原理就是元素的幾何屬性發(fā)生了改變,那么我們就從能夠改變元素幾何屬性的角度入手
添加或刪除可見的DOM元素
元素位置改變
元素本身的尺寸發(fā)生改變
內(nèi)容改變
頁面渲染器初始化
瀏覽器窗口大小發(fā)生改變
三. 如何進(jìn)行性能優(yōu)化重繪和重排的開銷是非常昂貴的,如果我們不停的在改變頁面的布局,就會造成瀏覽器耗費(fèi)大量的開銷在進(jìn)行頁面的計(jì)算,這樣的話,我們頁面在用戶使用起來,就會出現(xiàn)明顯的卡頓。現(xiàn)在的瀏覽器其實(shí)已經(jīng)對重排進(jìn)行了優(yōu)化,比如如下代碼:
var div = document.querySelector(".div"); div.style.width = "200px"; div.style.background = "red"; div.style.height = "300px";
比較久遠(yuǎn)的瀏覽器,這段代碼會觸發(fā)頁面2次重排,在分別設(shè)置寬高的時(shí)候,觸發(fā)2次,當(dāng)代的瀏覽器對此進(jìn)行了優(yōu)化,這種思路類似于現(xiàn)在流行的MVVM框架使用的虛擬DOM,對改變的DOM節(jié)點(diǎn)進(jìn)行依賴收集,確認(rèn)沒有改變的節(jié)點(diǎn),就進(jìn)行一次更新。但是瀏覽器針對重排的優(yōu)化雖然思路和虛擬DOM接近,但是還是有本質(zhì)的區(qū)別。大多數(shù)瀏覽器通過隊(duì)列化修改并批量執(zhí)行來優(yōu)化重排過程。也就是說上面那段代碼其實(shí)在現(xiàn)在的瀏覽器優(yōu)化下,只構(gòu)成一次重排。
但是還是有一些特殊的元素幾何屬性會造成這種優(yōu)化失效。比如:
offsetTop, offsetLeft,...
scrollTop, scrollLeft, ...
clientTop, clientLeft, ...
getComputedStyle() (currentStyle in IE)
為什么造成優(yōu)化失效呢?仔細(xì)看這些屬性,都是需要實(shí)時(shí)回饋給用戶的幾何屬性或者是布局屬性,當(dāng)然不能再依靠瀏覽器的優(yōu)化,因此瀏覽器不得不立即執(zhí)行渲染隊(duì)列中的“待處理變化”,并隨之觸發(fā)重排返回正確的值。
接下來深入的介紹幾種性能優(yōu)化的小TIPS
既然重排&重繪是會影響頁面的性能,尤其是糟糕的JS代碼更會將重排帶來的性能問題放大。既然如此,我們首先想到的就是減少重排重繪。
3.1.1. 改變樣式考慮下面這個(gè)例子:
// javascript var el = document.querySelector(".el"); el.style.borderLeft = "1px"; el.style.borderRight = "2px"; el.style.padding = "5px";
這個(gè)例子其實(shí)和上面那個(gè)例子是一回事兒,在最糟糕的情況下,會觸發(fā)瀏覽器三次重排。然鵝更高效的方式就是合并所有的改變一次處理。這樣就只會修改DOM節(jié)點(diǎn)一次,比如改為使用cssText屬性實(shí)現(xiàn):
var el = document.querySelector(".el"); el.style.cssText = "border-left: 1px; border-right: 2px; padding: 5px";
沿著這個(gè)思路,聰明的老鐵一定就說了,你直接改個(gè)類名不也妥妥的。沒錯(cuò),還有一種減少重排的方法就是切換類名,而不是使用內(nèi)聯(lián)樣式的cssText方法。使用切換類名就變成了這樣:
// css .active { padding: 5px; border-left: 1px; border-right: 2px; } // javascript var el = document.querySelector(".el"); el.className = "active";3.1.2 批量修改DOM
如果我們需要對DOM元素進(jìn)行多次修改,怎么去減少重排和重繪的次數(shù)呢?有的同學(xué)又要說了,利用上面修改樣式的方法不就行了嗎?;剡^頭看一下造成頁面重排的幾個(gè)要點(diǎn)里,可以明確的看到,造成元素幾何屬性發(fā)生改變就會觸發(fā)重排,現(xiàn)在需要增加10個(gè)節(jié)點(diǎn),必然涉及到DOM的修改,這個(gè)時(shí)候就需要利用批量修改DOM這種優(yōu)化方式了,這里也能看到,改變樣式最小化重繪和重排這種優(yōu)化方式適用于單個(gè)存在的節(jié)點(diǎn)。
批量修改DOM元素的核心思想是:
讓該元素脫離文檔流
對其進(jìn)行多重改變
將元素帶回文檔中
打個(gè)比方,我們主機(jī)硬盤出現(xiàn)了故障,常見的辦法就是把硬盤卸下來,用專業(yè)的工具測試哪里有問題,待修復(fù)后再安裝上去。要是直接在主板上面用螺絲刀弄來弄去,估計(jì)主板一會兒也要壞了...
這個(gè)過程引發(fā)倆次重排,第一步和第三步,如果沒有這兩步,可以想象一下,第二步每次對DOM的增刪都會引發(fā)一次重排。那么知道批量修改DOM的核心思想后,我們再了解三種可以使元素可以脫離文檔流的方法,注意,這里不使用css中的浮動&絕對定位,這是風(fēng)馬牛不相及的概念。
隱藏元素,進(jìn)行修改后,然后再顯示該元素
使用文檔片段創(chuàng)建一個(gè)子樹,然后再拷貝到文檔中
將原始元素拷貝到一個(gè)獨(dú)立的節(jié)點(diǎn)中,操作這個(gè)節(jié)點(diǎn),然后覆蓋原始元素
看一下下面這個(gè)代碼示例:
// html// javascript 現(xiàn)在需要添加帶有如下信息的li節(jié)點(diǎn) let data = [ { name: "tom", url: "https://www.baidu.com", }, { name: "ann", url: "https://www.techFE.com" } ]
首先,我們先寫一個(gè)通用的用于將新數(shù)據(jù)更新到指定節(jié)點(diǎn)的方法:
// javascript function appendNode($node, data) { var a, li; for(let i = 0, max = data.length; i < max; i++) { a = document.createElement("a"); li = document.createElement("li"); a.href = data[i].url; a.appendChild(document.createTextNode(data[i].name)); li.appendChild(a); $node.appendChild(li); } }
首先我們忽視所有的重排因素,大家肯定會這么寫:
let ul = document.querySelector("#mylist"); appendNode(ul, data);
使用這種方法,在沒有任何優(yōu)化的情況下,每次插入新的節(jié)點(diǎn)都會造成一次重排(這幾部分我們都先討論重排,因?yàn)橹嘏攀切阅軆?yōu)化的第一步)。考慮這個(gè)場景,如果我們添加的節(jié)點(diǎn)數(shù)量眾多,而且布局復(fù)雜,樣式復(fù)雜,那么能想到的是你的頁面一定非常卡頓。我們利用批量修改DOM的優(yōu)化手段來進(jìn)行重構(gòu)
1)隱藏元素,進(jìn)行修改后,然后再顯示該元素
let ul = document.querySelector("#mylist"); ul.style.display = "none"; appendNode(ul, data); ul.style.display = "block";
這種方法造成倆次重排,分別是控制元素的顯示與隱藏。對于復(fù)雜的,數(shù)量巨大的節(jié)點(diǎn)段落可以考慮這種方法。為啥使用display屬性呢,因?yàn)閐isplay為none的時(shí)候,元素就不在文檔流了,還不熟悉的老鐵,手動Google一下,display:none, opacity: 0, visibility: hidden的區(qū)別
2)使用文檔片段創(chuàng)建一個(gè)子樹,然后再拷貝到文檔中
let fragment = document.createDocumentFragment(); appendNode(fragment, data); ul.appendChild(fragment);
我是比較喜歡這種方法的,文檔片段是一個(gè)輕量級的document對象,它設(shè)計(jì)的目的就是用于更新,移動節(jié)點(diǎn)之類的任務(wù),而且文檔片段還有一個(gè)好處就是,當(dāng)向一個(gè)節(jié)點(diǎn)添加文檔片段時(shí),添加的是文檔片段的子節(jié)點(diǎn)群,自身不會被添加進(jìn)去。不同于第一種方法,這個(gè)方法并不會使元素短暫消失造成邏輯問題。上面這個(gè)例子,只在添加文檔片段的時(shí)候涉及到了一次重排。
3)將原始元素拷貝到一個(gè)獨(dú)立的節(jié)點(diǎn)中,操作這個(gè)節(jié)點(diǎn),然后覆蓋原始元素
let old = document.querySelector("#mylist"); let clone = old.cloneNode(true); appendNode(clone, data); old.parentNode.replaceChild(clone, old);
可以看到這種方法也是只有一次重排??偟膩碚f,使用文檔片段,可以操作更少的DOM(對比使用克隆節(jié)點(diǎn)),最小化重排重繪次數(shù)。
3.1.3 緩存布局信息緩存布局信息這個(gè)概念,在《高性能JavaScript》DOM性能優(yōu)化中,多次提到類似的思想,比如我現(xiàn)在要得到頁面ul節(jié)點(diǎn)下面的100個(gè)li節(jié)點(diǎn),最好的辦法就是第一次獲取后就保存起來,減少DOM的訪問以提升性能,緩存布局信息也是同樣的概念。前面有講到,當(dāng)訪問諸如offsetLeft,clientTop這種屬性時(shí),會沖破瀏覽器自有的優(yōu)化————通過隊(duì)列化修改和批量運(yùn)行的方法,減少重排/重繪版次。所以我們應(yīng)該盡量減少對布局信息的查詢次數(shù),查詢時(shí),將其賦值給局部變量,使用局部變量參與計(jì)算。
看以下樣例:
將元素div向右下方平移,每次移動1px,起始位置100px, 100px。性能糟糕的代碼:
div.style.left = 1 + div.offsetLeft + "px"; div.style.top = 1 + div.offsetTop + "px";
這樣造成的問題就是,每次都會訪問div的offsetLeft,造成瀏覽器強(qiáng)制刷新渲染隊(duì)列以獲取最新的offsetLeft值。更好的辦法就是,將這個(gè)值保存下來,避免重復(fù)取值
current = div.offsetLeft; div.style.left = 1 + ++current + "px"; div.style.top = 1 + ++current + "px";
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/99219.html
摘要:就如上面的概念一樣,單單改變元素的外觀,肯定不會引起網(wǎng)頁重新生成布局,但當(dāng)瀏覽器完成重排之后,將會重新繪制受到此次重排影響的部分。因?yàn)殛?duì)列中,可能會有影響到這些值的操作,為了給我們最精確的值,瀏覽器會立即重排重繪。 showImg(http://ww1.sinaimg.cn/large/005Y4rCogy1fya3fh2jm3j30ku0dwtb2.jpg); 很多人都知道要減少瀏覽...
摘要:瀏覽器會使用它來判斷文檔類型,決定使用何種協(xié)議來解析,以及切換瀏覽器模式。如果文件代碼不合法,那么瀏覽器解析時(shí)便會出一些差錯(cuò)。瀏覽器的工作流程解析,生成樹,解析生產(chǎn)根據(jù)生成的和構(gòu)建渲染樹根據(jù)渲染樹,計(jì)算每個(gè)節(jié)點(diǎn)在屏幕上的位置,尺寸等信息。 什么是DOCTYPE及作用DTD是一系列的語法規(guī)則,用來定義XML或(X)HTML的文件類型。瀏覽器會使用它來判斷文檔類型,決定使用何種協(xié)議來解析,...
摘要:關(guān)于網(wǎng)頁性能網(wǎng)頁性能管理是一個(gè)很大的話題,最近在復(fù)習(xí)相關(guān)的知識,小結(jié)一下。這兩個(gè)規(guī)則的實(shí)質(zhì)都是提高頁面的性能,避免發(fā)生不必要的重新渲染。頁面性能優(yōu)化重排和重繪會不斷觸發(fā),這是不可避免的。但是,它們非常耗費(fèi)資源,是導(dǎo)致網(wǎng)頁性能低下的根本原因。 關(guān)于網(wǎng)頁性能 網(wǎng)頁性能管理是一個(gè)很大的話題,最近在復(fù)習(xí)相關(guān)的知識,小結(jié)一下。 頁面加載順序 網(wǎng)頁生成的過程大致如下: HTML代碼轉(zhuǎn)化成DOM...
摘要:頁面性能優(yōu)化學(xué)而不思則惘,思而不學(xué)則殆前幾天接到一個(gè)頁面效果優(yōu)化的任務(wù),邊做邊查閱了一些關(guān)于頁面性能的資料??赡苤恍枰谥惺褂眠@類屬性,即可開啟硬件加速硬件加速真的那么好嗎從本人在移動端開發(fā)的實(shí)踐來看,硬件加速是比較坑的。 頁面性能優(yōu)化 學(xué)而不思則惘,思而不學(xué)則殆 前幾天接到一個(gè)頁面效果優(yōu)化的任務(wù),邊做邊查閱了一些關(guān)于頁面性能的資料。做完任務(wù)之后,抽空寫了一篇總結(jié),梳理一下思路,加深自...
摘要:頁面性能優(yōu)化學(xué)而不思則惘,思而不學(xué)則殆前幾天接到一個(gè)頁面效果優(yōu)化的任務(wù),邊做邊查閱了一些關(guān)于頁面性能的資料。可能只需要在中使用這類屬性,即可開啟硬件加速硬件加速真的那么好嗎從本人在移動端開發(fā)的實(shí)踐來看,硬件加速是比較坑的。 頁面性能優(yōu)化 學(xué)而不思則惘,思而不學(xué)則殆 前幾天接到一個(gè)頁面效果優(yōu)化的任務(wù),邊做邊查閱了一些關(guān)于頁面性能的資料。做完任務(wù)之后,抽空寫了一篇總結(jié),梳理一下思路,加深自...
閱讀 3000·2021-10-19 11:46
閱讀 991·2021-08-03 14:03
閱讀 2952·2021-06-11 18:08
閱讀 2923·2019-08-29 13:52
閱讀 2778·2019-08-29 12:49
閱讀 497·2019-08-26 13:56
閱讀 937·2019-08-26 13:41
閱讀 861·2019-08-26 13:35