原文鏈接 https://www.imooc.com/article/45936 侵刪
前端性能優(yōu)化因為涉及到計算機網(wǎng)絡(luò)货抄、數(shù)據(jù)算法述召、圖形圖像處理、瀏覽器渲染等多方面計算機知識蟹地,常作為前端工程師樂此不疲的技術(shù)討論話題积暖,也正因如此,它也是面試時容易被問及的面試題之一怪与。
緣起
本篇文章緣起一次偶然的面試問答所引申出的思考整理夺刑,著筆于瀏覽器渲染的角度,探討前端性能優(yōu)化的思路和實踐建議,當(dāng)然遍愿,瀏覽器渲染是一個復(fù)雜的過程存淫,本文筆者將圍繞重排和重繪兩個關(guān)鍵詞開始行文。
目錄結(jié)構(gòu)
文章大致行文思路如下:
URL從輸入到頁面展示的過程
DOM和JavaScript的關(guān)系
為什么操作DOM會很“慢”
瀏覽器解析HTML的過程
重排
重繪
優(yōu)化方案
URL從輸入到頁面展示的過程
在探討瀏覽器解析html之前沼填,先了解url從輸入到最后頁面渲染的過程是一個很有必要的步驟桅咆,它可以幫助我們把握整體流程,讓我們在了解HTML解析細(xì)節(jié)之前知道它處于整個請求周期中的哪一階段坞笙,這對我們構(gòu)建完善知識圖譜很有幫助岩饼。
首先,我們假設(shè)輸入的url的請求為最簡單的Http請求薛夜,以GET請求為例籍茧,大致分以下幾個步驟:
用戶在瀏覽器的地址欄輸入訪問的URL地址。瀏覽器會先根據(jù)這個URL查看瀏覽器緩存-系統(tǒng)緩存-路由器緩存梯澜,若緩存中有硕糊,直接跳到第6步操作,若沒有腊徙,則按照下面的步驟進行操作简十。
瀏覽器根據(jù)輸入的URL地址解析出主機名。
瀏覽器將主機名轉(zhuǎn)換成服務(wù)器ip地址撬腾。瀏覽器先查找本地DNS緩存列表螟蝙,看緩存里面是否存在這個ip,如果有則進入第4步,如果緩存中不存在這個ip地址民傻,就再向瀏覽器默認(rèn)的DNS服務(wù)器發(fā)送查詢請求胰默,同時緩存當(dāng)前這個ip到DNS緩存列表中。更詳細(xì)步驟參考DNS查找域名的過程漓踢。
拿到ip地址后牵署,瀏覽器再從URL中解析出端口號。
拿到ip和端口后喧半,瀏覽器會建立一條與目標(biāo)Web服務(wù)器的TCP連接奴迅,也就是傳說中的三次握手。傳送門:完整的tcp鏈接挺据。
瀏覽器向服務(wù)器發(fā)送一條HTTP請求報文取具。
服務(wù)器向瀏覽器返回一條HTTP響應(yīng)報文。
關(guān)閉連接 瀏覽器解析文檔扁耐。
如果文檔中有資源則重復(fù)6暇检、7、8動作婉称,直至資源全部加載完畢块仆。
以上步驟簡述了瀏覽器從輸入url到最后頁面呈現(xiàn)的大致過程构蹬,但這并不很具體,比如瀏覽器請求報文類型是什么悔据,會遇到哪些錯誤場景庄敛、瀏覽器又是如何解析響應(yīng)報文等等都沒具體描述。
實際上在http請求方式不同蜜暑、有無代理、有無負(fù)載均衡等不同場景下訪問服務(wù)器的細(xì)節(jié)流程也會有一些差別策肝,但這并不影響我們對整個訪問環(huán)節(jié)的理解肛捍,有興趣的同學(xué)可網(wǎng)上自行詳細(xì)了解,在此不做詳述之众。
DOM和JavaScript的關(guān)系
文檔對象模型(DOM)是一個獨立于語言拙毫,用于操作XML和HTML文檔的API,在web端,我們常用來操作HTML棺禾,但其實DOM也是可以操作XML文檔的缀蹄。
我們現(xiàn)在知道,DOM是一個獨立于語言的API膘婶,換句話說缺前,DOM是一個與語言無關(guān)的API,別的語言也可以實現(xiàn)操作DOM的具體api悬襟,但是它在瀏覽器中是用JavaScript來實現(xiàn)的衅码,也因此,DOM是現(xiàn)在JavaScript編碼中很重要的一部分脊岳,因為JavaScript很多時候都在操作底層文檔逝段。
為什么操作DOM會很慢
雖然DOM是由JavaScript實現(xiàn)的,但是在瀏覽器中都是把DOM和JavaScript分開來實現(xiàn)的割捅,比如IE中奶躯,JavaScript的實現(xiàn)名為JScript,放在jscript.dll文件中亿驾,而DOM則放在另一個叫做mshtml.dll的庫中嘹黔。在Safari中,DOM和渲染是使用Webkit中的WebCore實現(xiàn)莫瞬,而JavaScript是由獨立的JavaScriptCore引擎實現(xiàn)参淹,同樣在Chrome中,同樣是使用WebCore來實現(xiàn)渲染乏悄,而JavaScript引擎則是他們自己研發(fā)的V8引擎浙值。
由于DOM和JavaScript是被分開獨立實現(xiàn)的,因此檩小,每一次在通過js操作DOM的時候开呐,就需要先去連接js和DOM,我們可以這樣理解:把DOM和JavaScript比作兩個島,他們之間通過一個收費的橋連接著筐付,每一次訪問DOM的時候卵惦,就需要經(jīng)過這座橋辆沦,并且給“過路費”然磷,訪問的次數(shù)越多,路費就會越高空凸,并且訪問到DOM后较解,操作具體的DOM還需要給“操作費”畜疾,由于瀏覽器訪問DOM的操作很多,因此印衔,“路費”和“操作費”自然會增加啡捶,這就是為什么操作DOM會很慢的原因
瀏覽器渲染HTML的步驟
HTML渲染大致分為如下幾步:
HTML被HTML解析器解析成DOM Tree, css則被css解析器解析成CSSOM Tree。
DOM Tree和CSSOM Tree解析完成后奸焙,被附加到一起瞎暑,形成渲染樹(Render Tree)。
節(jié)點信息計算(重排)与帆,這個過程被叫做Layout(Webkit)或者Reflow(Mozilla)了赌。即根據(jù)渲染樹計算每個節(jié)點的幾何信息。
渲染繪制(重繪)玄糟,這個過程被叫做(Painting 或者 Repaint)揍拆。即根據(jù)計算好的信息繪制整個頁面。
以上4步簡述瀏覽器的一次渲染過程茶凳,理論上嫂拴,每一次的dom更改或者css幾何屬性更改,都會引起一次瀏覽器的重排/重繪過程贮喧,而如果是css的非幾何屬性更改筒狠,則只會引起重繪過程。所以說重排一定會引起重繪箱沦,而重繪不一定會引起重排辩恼。
重排(Relayout/Reflow)
在弄明白什么是重排之前,我們要知道:瀏覽器渲染頁面默認(rèn)采用的是流式布局模型(Flow Based Layout)谓形,這一點很重要灶伊。
所謂重排,實際上是根據(jù)渲染樹中每個渲染對象的信息寒跳,計算出各自渲染對象的幾何信息(DOM對象的位置和尺寸大衅溉),并將其安置在界面中的正確位置童太。
由于瀏覽器渲染界面是基于流式布局模型的米辐,也就是某一個DOM節(jié)點信息更改了胸完,就需要對DOM結(jié)構(gòu)進行重新計算,重新布局界面翘贮,再次引發(fā)回流赊窥,只是這個結(jié)構(gòu)更改程度會決定周邊DOM更改范圍,即全局范圍和局部范圍狸页,全局范圍就是從根節(jié)點html
開始對整個渲染樹進行重新布局锨能,例如當(dāng)我們改變了窗口尺寸或方向或者是修改了根元素的尺寸或者字體大小等;而局部布局可以是對渲染樹的某部分或某一個渲染對象進行重新布局芍耘。
在此址遇,總結(jié)會引起重排的操作有:
頁面首次渲染。
瀏覽器窗口大小發(fā)生改變齿穗。
元素尺寸或位置發(fā)生改變傲隶。
元素內(nèi)容變化(文字?jǐn)?shù)量或圖片大小等等)饺律。
元素字體大小變化窃页。
添加或者刪除可見的DOM元素。
激活CSS偽類(例如::hover)复濒。
設(shè)置style屬性
查詢某些屬性或調(diào)用某些方法脖卖。
常見引起重排屬性和方法 | |||
---|---|---|---|
width | height | margin | padding |
display | border | position | overflow |
clientWidth | clientHeight | clientTop | clientLeft |
offsetWidth | offsetHeight | offsetTop | offsetLeft |
scrollWidth | scrollHeight | scrollTop | scrollLeft |
scrollIntoView() | scrollTo() | getComputedStyle() | |
getBoundingClientRect() | scrollIntoViewIfNeeded() |
重排也叫回流,實際上巧颈,reflow的字面意思也是回流畦木,之所以有的叫做重排,也許是因為重排更好理解砸泛,更符合中國人的思維十籍。標(biāo)準(zhǔn)文檔之所以叫做回流(Reflow),是因為瀏覽器渲染是基于“流式布局”的模型,流實際就使我們常說的文檔流唇礁,當(dāng)dom或者css幾何屬性發(fā)生改變的時候勾栗,文檔流會受到波動聯(lián)動的去更改,流就好比一條河里的水盏筐,回流就好比向河里扔了一塊石頭围俘,激起漣漪,然后引起周邊水流受到波及琢融,所以叫做回流界牡,這樣理解似乎更標(biāo)準(zhǔn)更規(guī)范,不過叫什么并不重要漾抬,重要的是我們真正理解了這個過程便好宿亡。
重繪(Repainting)
相比重排,重繪就簡單多了纳令,所謂重繪她混,就是當(dāng)頁面中元素樣式的改變并不影響它在文檔流中的位置時烈钞,例如更改了字體顏色,瀏覽器會將新樣式賦予給元素并重新繪制的過程稱。
常見引起瀏覽器繪制過程的屬性包含:
color | border-style | visibility | background |
text-decoration | background-image | background-position | background-repeat |
outline-color | outline | outline-style | border-radius |
outline-width | box-shadow | background-size |
性能優(yōu)化
我們知道操作DOM是一個高成本的操作坤按,不僅是因為本身js與DOM的鏈接訪問毯欣,還包括操作DOM后悔引起一連串的連鎖反應(yīng)(重排),因此臭脓,從性能優(yōu)化角度酗钞,我們可以從以下幾個方面著手:
-
減少DOM操作
最小化DOM訪問次數(shù),盡量緩存訪問DOM的樣式信息来累,避免過度觸發(fā)回流砚作。
如果在一個局部方法中需要多次訪問同一個dom,則先暫存它的引用嘹锁。
-
采用更優(yōu)的API替代消費高的api葫录,轉(zhuǎn)換優(yōu)化消費高的集合
用querySelectorAll()替代getElementByXX()。
開啟動畫的GPU加速领猾,把渲染計算交給GPU米同。
少用HTML集合(類數(shù)組)來遍歷,因為集合遍歷比真數(shù)組遍歷耗費更高摔竿。
用事件委托來減少事件處理器的數(shù)量面粮。
-
減少重排
避免設(shè)置大量的style屬性,因為通過設(shè)置style屬性改變結(jié)點樣式的話继低,每一次設(shè)置都會觸發(fā)一次reflow熬苍,所以最好是使用class屬性
實現(xiàn)元素的動畫,它的position屬性袁翁,最好是設(shè)為absoulte或fixed柴底,這樣不會影響其他元素的布局
動畫實現(xiàn)的速度的選擇。比如實現(xiàn)一個動畫粱胜,以1個像素為單位移動這樣最平滑柄驻,但是reflow就會過于頻繁,大量消耗CPU資源年柠,如果以3個像素為單位移動則會好很多凿歼。
不要使用table布局,因為table中某個元素旦觸發(fā)了reflow冗恨,那么整個table的元素都會觸發(fā)reflow答憔。那么在不得已使用table的場合,可以設(shè)置table-layout:auto;或者是table-layout:fixed這樣可以讓table一行一行的渲染掀抹,這種做法也是為了限制reflow的影響范圍
-
css及動畫處理
少用css表達式
減少通過JavaScript代碼修改元素樣式虐拓,盡量使用修改class名方式操作樣式或動畫;
動畫盡量使用在絕對定位或固定定位的元素上傲武;
隱藏在屏幕外蓉驹,或在頁面滾動時城榛,盡量停止動畫;
最后總結(jié)
本篇文章主要抓取url從輸入到最后渲染成界面這一流程中的瀏覽器解析渲染HTML這一步驟來探討前端優(yōu)化的思路和原因态兴,核心思想基于重排和重繪的關(guān)系來展開討論狠持,主題大致有如下幾點:
url從輸入到最后渲染的大致環(huán)節(jié)。
重排一定會重繪瞻润,重繪不一定有重排喘垂。
Js操作DOM是一個高消費過程。
會引起重排/重繪的屬性和方法列舉
優(yōu)化思路(減少dom操作绍撞、替換高性能api正勒、暫存引用、減少重排傻铣、開啟硬件加速等)章贞。
最后,由于個人水平原因非洲,若有行文不全或疏漏錯誤之處鸭限,懇請各位讀者批評指正,一路有你怪蔑,不勝感激里覆!
作者:小白師兄
鏈接:https://www.imooc.com/article/45936
來源:慕課網(wǎng)
本文原創(chuàng)發(fā)布于慕課網(wǎng) 丧荐,轉(zhuǎn)載請注明出處缆瓣,謝謝合作