<script>標(biāo)簽可以放在html文件中的任何位置,但一般放在head或body標(biāo)簽里面。上次在視頻中聽老師講到最后放入head標(biāo)簽中,避免頁面出現(xiàn)抖屏現(xiàn)象,所以決定深入了解一下瀏覽器的渲染和加載機(jī)制。
<script>標(biāo)簽位置
1. 放在<head>里
<head>標(biāo)簽僅次于<title>標(biāo)簽,放在<head>中則此js代碼會(huì)在整個(gè)網(wǎng)頁最開始解析時(shí)就加載執(zhí)行,然后依次向下解析渲染。
注意:進(jìn)行頁面顯示初始化的js必須放在head里面
2. 放在<body>里
瀏覽器按照頁面標(biāo)簽的順序依次解析,在讀取到j(luò)s代碼時(shí)會(huì)執(zhí)行js語句。
注意:對于通過事件調(diào)用的JS函數(shù),具體放在頁面的哪個(gè)位置并不影響其發(fā)揮作用的時(shí)間,因此,考慮到前端性能方面的問題,可以把不是最先執(zhí)行的和事件調(diào)用的JS代碼放在body的最下面。
瀏覽器加載和渲染的順序
- IE下載的順序從上到下,渲染順序也是從上到下,下載和渲染同時(shí)進(jìn)行。
- 在渲染到頁面的某一部分時(shí),其上面的所有部分都已經(jīng)下載完成(并不是說所有相關(guān)聯(lián)的元素都已經(jīng)下載完)。
- 如果遇到語義解釋性的標(biāo)簽嵌入文件(JS腳本,CSS樣式),那么此時(shí)IE的下載過程會(huì)啟用單獨(dú)連接進(jìn)行下載。
- 樣式表在下載完成后,將和以前下載的所有樣式表一起進(jìn)行解析,解析完成后,將對此前所有元素(含以前已經(jīng)渲染的)重新進(jìn)行渲染。(也就是上面說的可能出現(xiàn)抖屏現(xiàn)象)
- JS、CSS中如有重定義,后定義函數(shù)將覆蓋前定義函數(shù)。
JS的加載
- 不能并行下載和解析(阻塞下載)。
- 當(dāng)引用了JS的時(shí)候,瀏覽器發(fā)送1個(gè)js request就會(huì)一直等待該request的返回。因?yàn)闉g覽器需要1個(gè)穩(wěn)定的DOM樹結(jié)構(gòu),而JS中很有可能有代碼直接改變了DOM樹結(jié)構(gòu),比如使用document.write或apppendChild,甚至是直接使用location.href進(jìn)行跳轉(zhuǎn),瀏覽器為了防止出現(xiàn)JS修改DOM樹,需要重新構(gòu)建DOM樹的情況,所以 就會(huì)阻塞其他的下載和呈現(xiàn)
加快HTML頁面加載速度
頁面減肥:
a. 頁面的肥瘦是影響加載速度最重要的因素。
b. 刪除不必要的空格、注釋。
c. 將inline的script和css移到外部文件。
d. 可以使用HTML Tidy來給HTML減肥,還可以使用一些壓縮工具來給JavaScript減肥。減少文件數(shù)量:
a. 減少頁面上引用的文件數(shù)量可以減少HTTP連接數(shù)。
b. 許多JavaScript、CSS文件可以合并最好合并,人家財(cái)幫子都把自己的JavaScript. functions和Prototype.js合并到一個(gè)base.js文件里去了。減少域名查詢:
a. DNS查詢和解析域名也是消耗時(shí)間的,所以要減少對外部JavaScript、CSS、圖片等資源的引用,不同域名的使用越少越好。緩存重用數(shù)據(jù):
a. 對重復(fù)使用的數(shù)據(jù)進(jìn)行緩存。優(yōu)化頁面元素加載順序:
a. 首先加載頁面最初顯示的內(nèi)容和與之相關(guān)的JavaScript和CSS,然后加載HTML相關(guān)的東西,像什么不是最初顯示相關(guān)的圖片、flash、視頻等很肥的資源就最后加載。減少inline JavaScript的數(shù)量:
a. 瀏覽器parser會(huì)假設(shè)inline JavaScript會(huì)改變頁面結(jié)構(gòu),所以使用inline JavaScript開銷較大。
b. 不要使用document.write()這種輸出內(nèi)容的方法,使用現(xiàn)代W3C DOM方法來為現(xiàn)代瀏覽器處理頁面內(nèi)容。使用現(xiàn)代CSS和合法的標(biāo)簽:
a. 使用現(xiàn)代CSS來減少標(biāo)簽和圖像,例如使用現(xiàn)代CSS+文字完全可以替代一些只有文字的圖片。
b. 使用合法的標(biāo)簽避免瀏覽器解析HTML時(shí)做“error correction”等操作,還可以被HTML Tidy來給HTML減肥。Chunk your content:
a. 不要使用嵌套table,而使用非嵌套table或者div。將基于大塊嵌套的table的layout分解成多個(gè)小table,這樣就不需要等到整個(gè)頁面(或大table)內(nèi)容全部加載完才顯示。指定圖像和table的大?。?br> a. 如果瀏覽器可以立即決定圖像或table的大小,那么它就可以馬上顯示頁面而不要重新做一些布局安排的工作。
b. 這不僅加快了頁面的顯示,也預(yù)防了頁面完成加載后布局的一些不當(dāng)?shù)母淖儭?br> c. image使用height和width。
HTML頁面加載和解析流程
用戶輸入網(wǎng)址(假設(shè)是個(gè)html頁面,并且是第一次訪問),瀏覽器向服務(wù)器發(fā)出請求,服務(wù)器返回html文件。
瀏覽器開始載入html代碼,發(fā)現(xiàn)<head>標(biāo)簽內(nèi)有一個(gè)<link>標(biāo)簽引用外部CSS文件。
瀏覽器又發(fā)出CSS文件的請求,服務(wù)器返回這個(gè)CSS文件。
瀏覽器繼續(xù)載入html中<body>部分的代碼,并且CSS文件已經(jīng)拿到手了,可以開始渲染頁面了。
瀏覽器在代碼中發(fā)現(xiàn)一個(gè)<img>標(biāo)簽引用了一張圖片,向服務(wù)器發(fā)出請求。此時(shí)瀏覽器不會(huì)等到圖片下載完,而是繼續(xù)渲染后面的代碼。
服務(wù)器返回圖片文件,由于圖片占用了一定面積,影響了后面段落的排布,因此瀏覽器需要回過頭來重新渲染這部分代碼。
瀏覽器發(fā)現(xiàn)了一個(gè)包含一行Javascript代碼的<script>標(biāo)簽,趕快運(yùn)行它。
Javascript腳本執(zhí)行了這條語句,它命令瀏覽器隱藏掉代碼中的某個(gè)<style>(style.display=”none”)。杯具啊,突然就少了這么一個(gè)元素,瀏覽器不得不重新渲染這部分代碼。
終于等到了</html>的到來,瀏覽器淚流滿面……
等等,還沒完,用戶點(diǎn)了一下界面中的“換膚”按鈕,Javascript讓瀏覽器換了一下<link>標(biāo)簽的CSS路徑。
瀏覽器召集了在座的各位<div><span><ul><li>們,“大伙兒收拾收拾行李,咱得重新來過……”,瀏覽器向服務(wù)器請求了新的CSS文件,重新渲染頁面。
- 問題1
加載過程中遇到外部css文件,瀏覽器另外發(fā)出一個(gè)請求,來獲取css文件。遇到圖片資源,瀏覽器也會(huì)另外發(fā)出一個(gè)請求,來獲取圖片資源。這是異步請求,并不會(huì)影響html文檔進(jìn)行加載,但是當(dāng)文檔加載過程中遇到j(luò)s文件,html文檔會(huì)掛起渲染(加載解析渲染同步)的線程,不僅要等待文檔中js文件加載完畢,還要等待解析執(zhí)行完畢,才可以恢復(fù)html文檔的渲染線程。 - 原因:
JS有可能會(huì)修改DOM,最為經(jīng)典的document.write,這意味著,在JS執(zhí)行完成前,后續(xù)所有資源的下載可能是沒有必要的,這是js阻塞后續(xù)資源下載的根本原因。 - 辦法:
可以將外部引用的js文件放在</body>前。
- 問題:
雖然css文件的加載不影響js文件的加載,但是卻影響js文件的執(zhí)行,即使js文件內(nèi)只有一行代碼,也會(huì)造成阻塞。 - 原因:
可能會(huì)有 var width = $('#id').width(),這意味著,js代碼執(zhí)行前,瀏覽器必須保證css文件已下載和解析完成。這也是css阻塞后續(xù)js的根本原因。 - 辦法:
當(dāng)js文件不需要依賴css文件時(shí),可以將js文件放在頭部css的前面。
- 注意:
不要在外部調(diào)用的js文件中調(diào)用運(yùn)行時(shí)間較長的函數(shù),如果一定要用,可以使用setTimeout函數(shù)。 - 原因:
瀏覽器有以上五個(gè)常駐線程
-- 瀏覽器GUI渲染線程
-- javascript引擎線程
-- 瀏覽器定時(shí)器觸發(fā)線程(setTimeout)
-- 瀏覽器事件觸發(fā)線程
-- 瀏覽器http異步請求線程(.jpg <link />這類請求)
由于 javascript引擎線程為單線程,當(dāng)js引擎線程(第二個(gè))進(jìn)行時(shí),會(huì)掛起其他一切線程,所以代碼都是先壓到隊(duì)列,采用先進(jìn)先出的方式運(yùn)行,這個(gè)時(shí)候3、4、5這三類線線程也會(huì)產(chǎn)生不同的異步事件。
預(yù)加載
現(xiàn)代瀏覽器存在 prefetch 優(yōu)化,瀏覽器會(huì)另外開啟線程,提前下載js、css文件,需要注意的是,預(yù)加載js并不會(huì)改變dom結(jié)構(gòu),他將這個(gè)工作留給主加載。
預(yù)加載網(wǎng)頁,利用空余時(shí)間來提前加載該網(wǎng)頁的后續(xù)網(wǎng)頁
<link rel="prefetch" href="http://">
為js腳本添加defer屬性,使得瀏覽器不等js腳本加載執(zhí)行完,就加載后面的圖片
<script defer="true" src="JavaScript.js" type="text/javascript"/>
解析
- DOM樹構(gòu)建過程是深度遍歷過程,當(dāng)前節(jié)點(diǎn)的所有子節(jié)點(diǎn)都構(gòu)建好后才會(huì)去構(gòu)建當(dāng)前節(jié)點(diǎn)的下一個(gè)兄弟節(jié)點(diǎn)。
2 . 將CSS解析成 CSS Rule Tree 。
根據(jù)DOM樹和CSSOM來構(gòu)造 Rendering Tree。注意:Rendering Tree 渲染樹并不等同于 DOM 樹,因?yàn)橐恍┫?Header 或 display:none 的東西就沒必要放在渲染樹中了。
有了Render Tree,瀏覽器已經(jīng)能知道網(wǎng)頁中有哪些節(jié)點(diǎn)、各個(gè)節(jié)點(diǎn)的CSS定義以及他們的從屬關(guān)系。下一步操作稱之為Layout,顧名思義就是計(jì)算出每個(gè)節(jié)點(diǎn)在屏幕中的位置。
-
再下一步就是繪制,即遍歷render樹,并使用UI后端層繪制每個(gè)節(jié)點(diǎn)。
image.png
上述這個(gè)過程是逐步完成的,為了更好的用戶體驗(yàn),渲染引擎將會(huì)盡可能早的將內(nèi)容呈現(xiàn)到屏幕上,并不會(huì)等到所有的html都解析完成之后再去構(gòu)建和布局render樹。它是解析完一部分內(nèi)容就顯示一部分內(nèi)容,同時(shí),可能還在通過網(wǎng)絡(luò)下載其余內(nèi)容。
Reflow(回流):
瀏覽器要花時(shí)間去渲染,當(dāng)它發(fā)現(xiàn)了某個(gè)部分發(fā)生了變化影響了布局,那就需要倒回去重新渲染。
Repaint(重繪):
如果只是改變了某個(gè)元素的背景顏色,文字顏色等,不影響元素周圍或內(nèi)部布局的屬性,將只會(huì)引起瀏覽器的repaint,重畫某一部分。
Reflow要比Repaint更花費(fèi)時(shí)間,也就更影響性能。所以在寫代碼的時(shí)候,要盡量避免過多的Reflow。
引起reflow的原因
- 頁面初始化
- 某些元素的尺寸變了
- 某些CSS的屬性發(fā)生變化了,如display:none
- 操作DOM時(shí),如添加子節(jié)點(diǎn)
減少 reflow/repaint
- 不要一條一條地修改 DOM 的樣式。與其這樣,還不如預(yù)先定義好 css 的 class,然后修改 DOM 的 className。
- 要把 DOM 結(jié)點(diǎn)的屬性值放在一個(gè)循環(huán)里當(dāng)成循環(huán)里的變量。
- 為動(dòng)畫的 HTML 元件使用 fixed 或 absoult 的 position,那么修改他們的 CSS 是不會(huì) reflow 的。
- 千萬不要使用 table 布局。因?yàn)榭赡芎苄〉囊粋€(gè)小改動(dòng)會(huì)造成整個(gè) table 的重新布局
CSS規(guī)范
CSS選擇符是從右到左進(jìn)行匹配的。從右到左!所以,#nav li 我們以為這是一條很簡單的規(guī)則,秒秒鐘就能匹配到想要的元素,但是,但是,但是,是從右往左匹配啊,所以,會(huì)去找所有的li,然后再去確定它的父元素是不是#nav。,因此,寫css的時(shí)候需要注意:
- dom深度盡量淺。
- 減少inline javascript、css的數(shù)量。
- 使用現(xiàn)代合法的css屬性。
- 不要為id選擇器指定類名或是標(biāo)簽,因?yàn)閕d可以唯一確定一個(gè)元素。
- 避免后代選擇符,盡量使用子選擇符。原因:子元素匹配符的概率要大于后代元素匹配符。-后代選擇符;#tp p{} 子選擇符:#tp>p{}
- 避免使用通配符,舉一個(gè)例子,.mod .hd *{font-size:14px;} 根據(jù)匹配順序,將首先匹配通配符,也就是說先匹配出通配符,然后匹配.hd(就是要對dom樹上的所有節(jié)點(diǎn)進(jìn)行遍歷他的父級(jí)元素),然后匹配.mod,這樣的性能耗費(fèi)可想而知.
總結(jié)
- JS的下載和執(zhí)行會(huì)阻塞DOM樹的構(gòu)建以及其它資源的下載。原因是瀏覽器需要一個(gè)穩(wěn)定的DOM樹結(jié)構(gòu) ,來防止JS修改DOM樹,導(dǎo)致需要重新構(gòu)建DOM樹的情況
- JS載入后馬上執(zhí)行,如果JS代碼中有執(zhí)行時(shí)間長的函數(shù),用SetTimeOut()
- CSS文件的加載雖不影響JS文件的加載,但卻可能影響到JS文件的執(zhí)行造成阻塞。因此必須保證CSS文件已下載和解析完成。
4.關(guān)于順序的問題,可以將CSS文件在最前面加載,再載入JS。并且對于事件調(diào)用型js代碼可以放到<body>標(biāo)簽的最末端 。
