1.網(wǎng)頁的解析過程
2. 瀏覽器渲染流程
3.回流和重繪解析
4.合成和性能優(yōu)化
5.defer和async屬性
1.網(wǎng)頁的解析過程
輸入網(wǎng)址,DNS解析眷茁,返回ip(服務(wù)器地址)芬首,一般情況下谈况,服務(wù)器會給我們返回一個index.html的網(wǎng)頁涉瘾。
瀏覽器解析html頁面要出,如果遇到css的話捉偏,會去服務(wù)器下載css文件倒得;遇到script標(biāo)簽的話,會加載和執(zhí)行對應(yīng)的js代碼夭禽;
把這些東西都下載下來之后霞掺,接下來瀏覽器的內(nèi)核和js引擎會對css讹躯,js菩彬,html進行相關(guān)的操作;
然后潮梯,一個網(wǎng)頁下載下來后骗灶,就是由我們的渲染引擎來幫助我們解析的。
1.1 瀏覽器內(nèi)核和js引擎
1.1.1 瀏覽器內(nèi)核
Rendering Engine秉馏,排版引擎耙旦,頁面渲染引擎。一般習(xí)慣將之稱為“瀏覽器內(nèi)核”萝究,主要功能是解析HTML/CSS進行渲染頁面免都,渲染引擎決定了瀏覽器如何顯示網(wǎng)頁的內(nèi)容以及頁面的格式信息。
1帆竹、 IE瀏覽器內(nèi)核:Trident內(nèi)核绕娘,也是俗稱的IE內(nèi)核;
2栽连、Chrome瀏覽器內(nèi)核:Blink內(nèi)核险领;
3侨舆、Firefox瀏覽器內(nèi)核:Gecko內(nèi)核,俗稱Firefox內(nèi)核绢陌;
4挨下、Safari瀏覽器內(nèi)核:Webkit內(nèi)核;
1.1.2 js引擎
js引擎是解析下面,執(zhí)行js代碼复颈。
chrom,node:v8
webkit:JavaScriptCore
2. 渲染引擎如何解析頁面
1.HTML解析過程
先下載index.html沥割,所有的解析過程都是通過index.html開始的耗啦。
瀏覽器內(nèi)核里面有個HTML Parser;它通過Parser將html轉(zhuǎn)換成dom樹机杜,
2.生成css規(guī)則
css也會被css Parser進行解析帜讲,解析成css規(guī)則(display:xxx;color:xxx);
首先椒拗,如果是head里面寫的style樣式似将,那么就不用下載了,就直接一個單獨的線程幫助我們解析css蚀苛,生成css規(guī)則
遇到link的時候在验,瀏覽器會使用一個獨立的線程下載對應(yīng)的css文件,(下載css也不會影響dom解析)把
css文件下載下來之后堵未,也會有一個單獨的線程會幫助我們解析css規(guī)則腋舌。下載css是不會阻塞dom解析的,你html Parser該生成dom樹生成dom樹渗蟹,繼續(xù)解析html块饺。
但是需要注意的是如果html parser已經(jīng)解析完html,生成好了dom tree雌芽,但是這個時候css parser并沒有生成好style rules(規(guī)則樹 css om /css object model)授艰,那么這個時候沒辦法生成render tree。webkit的話世落,這個時候html parser會等到sytle rules生成淮腾,等兩個都有的時候,我在生成render tree屉佳。
總結(jié)就是解析css并不影響dom tree的生成来破,但是影響render tree的生成。
3.構(gòu)建Render Tree
就是css規(guī)則(cssom)和dom樹結(jié)合到一起變成attachment忘古,然后就生成了render tree渲染樹;
5.布局和繪制
- render tree只是告訴我們要顯示什么節(jié)點诅诱,節(jié)點里面有什么樣式髓堪,但是不會有節(jié)點的位置,大小信息,尺寸之類的干旁。
布局就是確認render tree上面的所有節(jié)點的寬度驶沼,高度和位置信息。
有了layout之后争群,我們可以確認每個節(jié)點的里面的樣式回怜,里面的內(nèi)容位置信息,大小信息换薄。 - 一旦有了布局layout玉雾,我們就可以繪制了paint。
paint就是將每個box盒子轉(zhuǎn)換成屏幕上實際的像素點轻要。
包括文本复旬,顏色,邊框冲泥,陰影驹碍,替換元素等。
3.回流和重繪
3.1回流(重排)
1.理解回流reflow
- 第一次確定節(jié)點的大小和位置凡恍,稱之為布局(layout)志秃。
- 之后對節(jié)點的大小、位置修改重新計算稱之為回流嚼酝。
2.什么情況下引起回流呢
1. 比如DOM結(jié)構(gòu)發(fā)生改變(添加新的節(jié)點或者移除節(jié)點)浮还;
添加新節(jié)點或者移除節(jié)點,其他dom的位置也會發(fā)生變化
--> domtree發(fā)生了變化革半,所以render tree一定會變化碑定,render tree變化了,就要重新計算layout又官,就是每個節(jié)點的尺寸延刘,大小,在瀏覽器的位置都得重新計算六敬。重新計算之后就得重新繪制碘赖,painting,就是轉(zhuǎn)換為實際的像素點顯示在屏幕上外构。
比如改變了布局(修改了width普泡、height、padding审编、font-size等值)
改變字體大小改變布局是因為span元素是由文字大小撐起來的撼班,字體變大,span這個盒子變大垒酬,就得重新繪制砰嘁。
boxEl.style.height="200px"
修改某個節(jié)點的高度的話件炉,dom 樹沒有發(fā)生改變,render樹沒有發(fā)生改變矮湘,但是節(jié)點的大小改變了斟冕,就需要重新進行l(wèi)ayout計算,重繪缅阳。
改變文字顏色的話磕蛇,是不需要做重新布局的,因為節(jié)點的內(nèi)容十办,大小秀撇,在瀏覽器的位置都沒有改變,不需要重新布局橘洞。
如果修改了某個東西之后捌袜,你得再次計算這些元素的布局(node 的節(jié)點大小,位置信息)炸枣,不論一個節(jié)點還是多個節(jié)點虏等,有些時候你改了一個,影響其他的節(jié)點适肠,這個時候也得重新layout霍衫。
如果一個元素是絕對定位,脫離了標(biāo)準(zhǔn)流侯养,改變它對其他節(jié)點沒影響敦跌,但是它本身大小發(fā)生變化,也會重新layout逛揩。
總結(jié)就是對節(jié)點的大小柠傍,位置修改重新計算的,就叫做回流比如窗口resize(修改了窗口的尺寸等)
比如flex布局辩稽,窗口縮小之后惧笛,一行放不下,就放到第二行逞泄,float布局也是患整。-
比如調(diào)用getComputedStyle方法獲取尺寸、位置信息喷众;這個取決于瀏覽器各谚。
獲取顏色信息不會,但是獲取尺寸的話到千,就會重新construct frames構(gòu)建frame昌渤,就會重新layout
3.2 重繪repaint
1.理解重繪
- 第一次渲染內(nèi)容稱之為繪制(paint)。
- 之后重新渲染稱之為重繪憔四。
2.什么情況下引起重繪呢
比如修改背景色愈涩、文字顏色望抽、邊框顏色、樣式(實線變成虛線)等履婉;
但是如果改變border-width,就會回流斟览,重新layout毁腿,因為節(jié)點大小改變,boder-style,bordr-color就只是引起重繪苛茂。
3.3回流和重繪之間的關(guān)系
- 回流一定會引起重繪已烤,所以回流是一件很消耗性能的事情。
- 所以在開發(fā)中要盡量避免發(fā)生回流:
3.4 如何盡量避免發(fā)生回流
? 1.修改樣式時盡量一次性修改
比如通過cssText修改妓羊,比如通過添加class修改
比如這個樣子
box.style.width="200px"
box.style.height="200px"
這個樣子會引起2次回流胯究,不過放在同一個腳本里面的話拧抖,瀏覽器會最后房子一起只進行一次重繪庐完。但是依賴于瀏覽器贞间,所以我們還是避免進行這個樣子的操作村象。樣式盡量一次性修改完成腐碱。比如cssText進行修改笋鄙,或者動態(tài)地添加class掠拳,在class里面把css寫好冒掌。
? 2.盡量避免頻繁的操作DOM
我們可以在一個DocumentFragment或者父元素中淹父,將要操作的DOM操作完成株婴,再一次性的操作;
把操作放到父元素里面暑认,操作完成之后困介,最后再添加到dom里面。
這也是虛擬dom提高性能的原因蘸际。
? 3.盡量避免通過getComputedStyle獲取尺寸座哩、位置等信息;
? 4.對某些元素使用position的absolute或者fixed
并不是不會引起回流捡鱼,而是開銷相對較小八回,不會對其他元素造成影響。 改變absolute或者fixed的大小之后驾诈,指會對自己的大小進行重新計算缠诅,重新layout,其他節(jié)點不需要重新計算乍迄,整體來說計算量相對小一些管引。
4.合成和性能優(yōu)化
4.1 特殊解析-composite合成
繪制的過程,可以將布局后的元素會知道多個合成圖層中闯两。這是瀏覽器的一種優(yōu)化手段褥伴。
默認情況下谅将,標(biāo)準(zhǔn)流中的內(nèi)容都是被繪制在同一個圖層layer中的。
如果當(dāng)前元素是在標(biāo)準(zhǔn)流里面的重慢,經(jīng)過各自解析饥臂,變成render tree,經(jīng)過layout似踱,生成了一種樹結(jié)構(gòu)隅熙,也就是layout tree。 如果這些元素都是標(biāo)準(zhǔn)流里面的核芽,那么會生成render layer囚戚,渲染圖層。
如果有個元素position:absolute轧简,他會生成另外一個render layer驰坊。
然后它會將我們多個render layer做一個合成。而一些特殊的屬性哮独,會創(chuàng)建一個新的合成層( CompositingLayer )拳芙,并且新的圖層可以利用GPU來加速繪制;
除了標(biāo)準(zhǔn)流和固定定位之類的會被分別創(chuàng)建圖層,然后放到一個合成層里面借嗽, 一些特殊的元素态鳖,也會創(chuàng)建一個新的合成層,compositing layer恶导。
因為新的合成層的每一個單獨的合成層都是單獨渲染的浆竭,都是可以利用GPU來加速繪制的。
就是如果生成一份單獨的合成層惨寿,原來的哪些合成詞就不需要動邦泄,只需要單獨改這一個合成層,并且進行渲染裂垦。
4.2 那么哪些屬性可以形成新的合成層呢
- 3D transforms
- video顺囊、canvas、iframe
- opacity 動畫轉(zhuǎn)換時蕉拢;
- position: fixed(absolute是普通的圖層特碳,不會生成 新的圖層)
- will-change:一個實驗性的屬性,提前告訴瀏覽器元素可能發(fā)生哪些變化晕换;
- animation 或 transition 設(shè)置了opacity午乓、transform;
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
#test,
#container {
height: 100px;
width: 100px;
background-color: yellow;
}
#container {
background-color: red;
/* position: fixed; 新的圖層*/
/* transform: translateZ(0);新的圖層*/
transition: transform 1s ease;
/* 利用transform去執(zhí)行動畫的時候闸准,對瀏覽器來說性能會更高益愈,因為他是在獨立 的圖層里面給你做動畫,*/
}
#container:hover {
transform: translateX(100px); /* 不會引起回流 */
/* margin-left:100px這樣的話就是在默認圖層里面,效率很低 蒸其,而且會引起回流*/
}
</style>
</head>
<body>
<div id="test">test</div>
<div id="container"></div>
</body>
</html>
4.3 缺點
分層確實可以提高性能敏释,但是它以內(nèi)存管理為代價,因此不應(yīng)作為 web 性能優(yōu)化策略的一部分過度使用摸袁。
5.defer和async屬性
5.1 script元素和頁面解析的關(guān)系
1. js加載執(zhí)行過程
- 瀏覽器在解析HTML的過程中钥顽,遇到了script元素是不能繼續(xù)構(gòu)DOM樹的;
- 它會停止繼續(xù)構(gòu)建但惶,首先下載JavaScript代碼耳鸯,并且執(zhí)行JavaScript的腳本;
- 只有等到JavaScript腳本執(zhí)行結(jié)束后膀曾,才會繼續(xù)解析HTML,構(gòu)建DOM樹阳啥;
2. 原因
-
這是因為JavaScript的作用之一就是操作DOM添谊,并且可以修改DOM;js代碼可以直接對dom樹進行操作
紫色三角形里面指的是js代碼對dom進行操作察迟。
- 如果我們等到DOM樹構(gòu)建完成并且渲染再執(zhí)行JavaScript斩狱,修改dom, 會造成嚴(yán)重的回流和重繪扎瓶,影響頁面的性能所踊;
- 所以會在遇到script元素時,優(yōu)先下載和執(zhí)行JavaScript代碼概荷,再繼續(xù)構(gòu)建DOM樹秕岛;
3.問題
- 在目前的開發(fā)模式中(比如Vue、React)误证,腳本往往比HTML頁面更“重”继薛,處理時間需要更長;
- 所以會造成頁面的解析阻塞愈捅,在腳本下載遏考、執(zhí)行完成之前,用戶在界面上什么都看不到蓝谨;
注意:dom樹什么時候解析完灌具,是html元素解析完成之后才算解析完。不解析完就不會生成render樹之類的譬巫,界面沒有顯示咖楣。
在div后面又一大堆script標(biāo)簽,一部分瀏覽器為了優(yōu)化缕题,會將這部分div先進行dom截歉,然后render tree,layout painting烟零,進行顯示出來瘪松。但是script后面的dom元素是不會進行顯示的咸作。
4.解決方法
為了解決這個問題,script元素給我們提供了兩個屬性(attribute):defer和async宵睦。
5.2 defer屬性
- defer 屬性告訴瀏覽器不要等待腳本下載记罚,而繼續(xù)解析HTML,構(gòu)建DOM Tree壳嚎。 腳本會由瀏覽器來進行單獨地下載桐智,但是不會阻塞DOM Tree的構(gòu)建過程;
如果腳本提前下載好了烟馅,也不會立即執(zhí)行说庭,它會等待DOM Tree構(gòu)建完成,在DOMContentLoaded事件之前先執(zhí)行defer中的代碼郑趁;
DOMContentLoaded事件就是內(nèi)容加載完畢刊驴,也就是dom樹構(gòu)建完成的時候。瀏覽器先去執(zhí)行defer中的代碼寡润,因為defer里面的js有可能操作dom捆憎。然后再去回調(diào)DOMContentLoaded事件。 - 所以DOMContentLoaded總是會等待defer中的代碼先執(zhí)行完成梭纹。
- 另外多個帶defer的腳本是可以保持正確的順序執(zhí)行的躲惰。
? 從某種角度來說,defer可以提高頁面的性能变抽,并且推薦放到head元素中础拨;
? 注意:defer僅適用于外部腳本,對于script默認內(nèi)容會被忽略瞬沦。
5.2.1 舉例1:不阻塞后面的東西去構(gòu)建太伊。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="app">app</div>
<div id="title">title</div>
<div id="nav">nav</div>
<div id="product">product</div>
<!-- 加上defer以后,js文件的下載和執(zhí)行逛钻,不會影響后面的DOM Tree的構(gòu)建 -->
<script src="./test.js"></script>
<h1>哈哈哈哈</h1>
</body>
</html>
test.js
console.log('test')
debugger
var boxEl = document.querySelector('.box')
console.log(boxEl)
這個時候如果沒有defer僚焦,打開頁面,打開開發(fā)者模式曙痘,之后刷新芳悲,哈哈哈是沒有加載出來的,因為debugger阻塞了dom渲染边坤。
<body>
<div id="app">app</div>
<div id="title">title</div>
<div id="nav">nav</div>
<div id="product">product</div>
<script src="./test.js" defer></script>
<h1>哈哈哈哈</h1>
</body>
如果給script標(biāo)簽defer屬性名扛,是會出現(xiàn)哈哈哈的,因為defer不會影響dom的渲染茧痒。defer你自己去一邊下載去了肮韧,你下載好以后等著,等我對dom樹生成好以后去執(zhí)行你。
5.2.2 DOMContentLoaded事件之前執(zhí)行
<body>
<div id="app">app</div>
<div id="title">title</div>
<div id="nav">nav</div>
<div id="product">product</div>
<!-- 加上defer以后弄企,js文件的下載和執(zhí)行超燃,不會影響后面的DOM Tree的構(gòu)建 -->
<script src="./test.js" defer></script>
<script>
window.addEventListener('DOMContentLoaded', () => {
console.log('DOMContentLoaded')
})
</script>
<h1 class="box">哈哈哈哈</h1>
</body>
test.js
console.log('test')
var boxEl = document.querySelector('.box')
console.log(boxEl)
打印結(jié)果
test
test.js:3 <h1 class="box">哈哈哈哈</h1>
index.html:18 DOMContentLoaded
defer是在DOMContentLoaded事件執(zhí)行的。在defer中是可以操作dom的拘领,因為dom樹已經(jīng)構(gòu)建完成了意乓。
總結(jié)1:加上defer以后,js文件的下載和執(zhí)行约素,不會影響后面的DOM Tree的構(gòu)建
總結(jié)2: 在defer中是可以操作dom的届良,因為dom樹已經(jīng)構(gòu)建完成了。
總結(jié)3:defer代碼是在DOMContentLoaded事件觸發(fā)之前執(zhí)行的圣猎。
總結(jié)4:另外多個帶defer的腳本是可以保持正確的順序執(zhí)行的
test.js
console.log('------------------------------')
console.log('i am test js')
var boxEl = document.querySelector('.box')
console.log(boxEl)
console.log('set message to "test message"')
var message = 'test message'
console.log('------------------------------')
demo.js
console.log('------------------------------')
console.log('i am demo js')
console.log(message)
console.log('------------------------------')
index.html
<body>
<div id="app">app</div>
<div id="title">title</div>
<div id="nav">nav</div>
<div id="product">product</div>
<!-- 加上defer以后士葫,js文件的下載和執(zhí)行,不會影響后面的DOM Tree的構(gòu)建 -->
<script src="./test.js" defer></script>
<script src="./demo.js" defer></script>
<script>
window.addEventListener('DOMContentLoaded', () => {
console.log('DOMContentLoaded')
})
</script>
<h1 class="box">哈哈哈哈</h1>
</body>
test.js一定是在demo之前執(zhí)行的送悔,就是意味著demo可以使用test中的變量
------------------------------
test.js:2 i am test js
test.js:4 <h1 class="box">哈哈哈哈</h1>
test.js:5 set message to "test message"
test.js:7 ------------------------------
demo.js:1 ------------------------------
demo.js:2 i am demo js
demo.js:3 test message
demo.js:4 ------------------------------
index.html:19 DOMContentLoaded
從某種角度來說为障,defer可以提高頁面的性能,并且推薦放到head元素中放祟;
提高頁面性能是因為使用defer不會阻塞dom tree的構(gòu)建 。
一般把defer放到head里面呻右,提前告訴瀏覽器跪妥,讓瀏覽器先去下載,domtree構(gòu)建声滥,比起放到最后讓他下載會比較提高速度眉撵。dom tree構(gòu)建完了,你才去下載落塑,瀏覽器還是要等纽疟。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script src="./test.js" defer></script>
<script src="./demo.js" defer></script>
</head>
<body>
<div id="app">app</div>
<div id="title">title</div>
<div id="nav">nav</div>
<div id="product">product</div>
<script>
window.addEventListener('DOMContentLoaded', () => {
console.log('DOMContentLoaded')
})
</script>
<h1 class="box">哈哈哈哈</h1>
</body>
</html>
這么加是沒有意義的。
defer僅適用于外部腳本憾赁,對于script默認內(nèi)容會被忽略污朽。
<body>
<div id="app">app</div>
<div id="title">title</div>
<div id="nav">nav</div>
<div id="product">product</div>
<script defer>
window.addEventListener('DOMContentLoaded', () => {
console.log('DOMContentLoaded')
})
</script>
<h1 class="box">哈哈哈哈</h1>
</body>
5.3 async屬性
- async 特性與 defer 有些類似,它也能夠讓腳本不阻塞頁面.
舉例:
test.js
console.log('------------------------------')
console.log('i am test js')
debugger
var boxEl = document.querySelector('.box')
console.log(boxEl)
console.log('set message to "test message"')
var message = 'test message'
console.log('------------------------------')
index.html
<body>
<div id="app">app</div>
<div id="title">title</div>
<div id="nav">nav</div>
<div id="product">product</div>
<script src="./test.js" async></script>
<script defer>
window.addEventListener('DOMContentLoaded', () => {
console.log('DOMContentLoaded')
})
</script>
<h1 class="box">哈哈哈哈</h1>
</body>
然后打開頁面龙考,打開開發(fā)者工具蟆肆,刷新頁面,發(fā)現(xiàn)哈哈哈也是顯示的晦款。
DOMContentLoaded
test.js:1 ------------------------------
test.js:2 i am test js
打印結(jié)果
- async是讓一個腳本完全獨立的
- async腳本不能保證順序炎功,它是獨立下載、獨立運行缓溅,不會等待其他腳本蛇损;
- async不會能保證在DOMContentLoaded之前或者之后執(zhí)行;
它只要下載下來之后就立刻馬上執(zhí)行,不用等到dom樹構(gòu)建完成以后才執(zhí)行淤齐。
所以它用起來比較危險股囊,1. 它不能隨意的操作dom,有可能它下載下來執(zhí)行的時候dom樹構(gòu)建完成床玻,也有可能下載完成執(zhí)行的時候dom樹還沒構(gòu)建完成毁涉。這時候拿到的就是null。如果想使用锈死,就判斷一下是不是null贫堰,不是的話再使用。
- 而且他也不能保證其他async腳本的順序待牵。
<body>
<div id="app">app</div>
<div id="title">title</div>
<div id="nav">nav</div>
<div id="product">product</div>
<script src="./test.js" async></script>
<script src="./demo.js" async></script>
<script>
window.addEventListener('DOMContentLoaded', () => {
console.log('DOMContentLoaded')
})
</script>
<h1 class="box">哈哈哈哈</h1>
</body>
這里面的test和demo兩個js文件其屏,并不能保證誰先下載執(zhí)行完。
這個代碼里面缨该,
DOMContentLoaded和demo偎行,test誰先執(zhí)行是不知道的,沒有順序贰拿。
- defer通常用于需要在文檔解析后操作DOM的JavaScript代碼蛤袒,并且對多個script文件有順序要求的;
- async通常用于獨立的腳本膨更,對其他腳本妙真,甚至DOM沒有依賴的;
6.瀏覽器對渲染內(nèi)容進行優(yōu)化
瀏覽器對標(biāo)準(zhǔn)進行改進荚守,對渲染內(nèi)容進行優(yōu)化珍德,不會等到整個html標(biāo)簽解析完成以后再去構(gòu)建dom tree,再render tree等等進行顯示矗漾。
比如說是一個script里面有代碼锈候,寫了個debuuger,打開頁面敞贡,打開開發(fā)者工具泵琳,執(zhí)行,這個時候嫡锌,script阻塞了dom樹的生成虑稼,頁面應(yīng)該啥也沒有,但是script以前的dom元素是可以看見的势木,這是因為瀏覽器對渲染內(nèi)容做了優(yōu)化蛛倦。
渲染引擎會力求盡快地將內(nèi)容顯示再屏幕上,它不必等到整個html文檔解析完畢之后啦桌,就開始構(gòu)建render tree和layout溯壶,painting及皂。 這也就是script前面的dom可以看見的原因。但是script阻塞dom樹渲染且改,所以script后面的哈哈哈是看不見的验烧。
標(biāo)準(zhǔn)是標(biāo)準(zhǔn),瀏覽器怎么實現(xiàn)標(biāo)準(zhǔn)就不一定了又跛。