瀏覽器渲染與加載機(jī)制

<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頁面加載速度

  1. 頁面減肥:
    a. 頁面的肥瘦是影響加載速度最重要的因素。
    b. 刪除不必要的空格、注釋。
    c. 將inline的script和css移到外部文件。
    d. 可以使用HTML Tidy來給HTML減肥,還可以使用一些壓縮工具來給JavaScript減肥。

  2. 減少文件數(shù)量:
    a. 減少頁面上引用的文件數(shù)量可以減少HTTP連接數(shù)。
    b. 許多JavaScript、CSS文件可以合并最好合并,人家財(cái)幫子都把自己的JavaScript. functions和Prototype.js合并到一個(gè)base.js文件里去了。

  3. 減少域名查詢:
    a. DNS查詢和解析域名也是消耗時(shí)間的,所以要減少對外部JavaScript、CSS、圖片等資源的引用,不同域名的使用越少越好。

  4. 緩存重用數(shù)據(jù):
    a. 對重復(fù)使用的數(shù)據(jù)進(jìn)行緩存。

  5. 優(yōu)化頁面元素加載順序:
    a. 首先加載頁面最初顯示的內(nèi)容和與之相關(guān)的JavaScript和CSS,然后加載HTML相關(guān)的東西,像什么不是最初顯示相關(guān)的圖片、flash、視頻等很肥的資源就最后加載。

  6. 減少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)容。

  7. 使用現(xiàn)代CSS和合法的標(biāo)簽:
    a. 使用現(xiàn)代CSS來減少標(biāo)簽和圖像,例如使用現(xiàn)代CSS+文字完全可以替代一些只有文字的圖片。
    b. 使用合法的標(biāo)簽避免瀏覽器解析HTML時(shí)做“error correction”等操作,還可以被HTML Tidy來給HTML減肥。

  8. Chunk your content:
    a. 不要使用嵌套table,而使用非嵌套table或者div。將基于大塊嵌套的table的layout分解成多個(gè)小table,這樣就不需要等到整個(gè)頁面(或大table)內(nèi)容全部加載完才顯示。

  9. 指定圖像和table的大?。?br> a. 如果瀏覽器可以立即決定圖像或table的大小,那么它就可以馬上顯示頁面而不要重新做一些布局安排的工作。
    b. 這不僅加快了頁面的顯示,也預(yù)防了頁面完成加載后布局的一些不當(dāng)?shù)母淖儭?br> c. image使用height和width。


HTML頁面加載和解析流程

  1. 用戶輸入網(wǎng)址(假設(shè)是個(gè)html頁面,并且是第一次訪問),瀏覽器向服務(wù)器發(fā)出請求,服務(wù)器返回html文件。

  2. 瀏覽器開始載入html代碼,發(fā)現(xiàn)<head>標(biāo)簽內(nèi)有一個(gè)<link>標(biāo)簽引用外部CSS文件。

  3. 瀏覽器又發(fā)出CSS文件的請求,服務(wù)器返回這個(gè)CSS文件。

  4. 瀏覽器繼續(xù)載入html中<body>部分的代碼,并且CSS文件已經(jīng)拿到手了,可以開始渲染頁面了。

  5. 瀏覽器在代碼中發(fā)現(xiàn)一個(gè)<img>標(biāo)簽引用了一張圖片,向服務(wù)器發(fā)出請求。此時(shí)瀏覽器不會(huì)等到圖片下載完,而是繼續(xù)渲染后面的代碼。

  6. 服務(wù)器返回圖片文件,由于圖片占用了一定面積,影響了后面段落的排布,因此瀏覽器需要回過頭來重新渲染這部分代碼。

  7. 瀏覽器發(fā)現(xiàn)了一個(gè)包含一行Javascript代碼的<script>標(biāo)簽,趕快運(yùn)行它。

  8. Javascript腳本執(zhí)行了這條語句,它命令瀏覽器隱藏掉代碼中的某個(gè)<style>(style.display=”none”)。杯具啊,突然就少了這么一個(gè)元素,瀏覽器不得不重新渲染這部分代碼。

  9. 終于等到了</html>的到來,瀏覽器淚流滿面……

  10. 等等,還沒完,用戶點(diǎn)了一下界面中的“換膚”按鈕,Javascript讓瀏覽器換了一下<link>標(biāo)簽的CSS路徑。

  11. 瀏覽器召集了在座的各位<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"/>

解析

  1. 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 。

  1. 根據(jù)DOM樹和CSSOM來構(gòu)造 Rendering Tree。注意:Rendering Tree 渲染樹并不等同于 DOM 樹,因?yàn)橐恍┫?Header 或 display:none 的東西就沒必要放在渲染樹中了。

  2. 有了Render Tree,瀏覽器已經(jīng)能知道網(wǎng)頁中有哪些節(jié)點(diǎn)、各個(gè)節(jié)點(diǎn)的CSS定義以及他們的從屬關(guān)系。下一步操作稱之為Layout,顧名思義就是計(jì)算出每個(gè)節(jié)點(diǎn)在屏幕中的位置。

  3. 再下一步就是繪制,即遍歷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é)

  1. JS的下載和執(zhí)行會(huì)阻塞DOM樹的構(gòu)建以及其它資源的下載。原因是瀏覽器需要一個(gè)穩(wěn)定的DOM樹結(jié)構(gòu) ,來防止JS修改DOM樹,導(dǎo)致需要重新構(gòu)建DOM樹的情況
  2. JS載入后馬上執(zhí)行,如果JS代碼中有執(zhí)行時(shí)間長的函數(shù),用SetTimeOut()
  3. CSS文件的加載雖不影響JS文件的加載,但卻可能影響到JS文件的執(zhí)行造成阻塞。因此必須保證CSS文件已下載和解析完成。
    4.關(guān)于順序的問題,可以將CSS文件在最前面加載,再載入JS。并且對于事件調(diào)用型js代碼可以放到<body>標(biāo)簽的最末端 。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 第一部分 HTML&CSS整理答案 1. 什么是HTML5? 答:HTML5是最新的HTML標(biāo)準(zhǔn)。 注意:講述HT...
    kismetajun閱讀 28,771評論 1 45
  • 問答題47 /72 常見瀏覽器兼容性問題與解決方案? 參考答案 (1)瀏覽器兼容問題一:不同瀏覽器的標(biāo)簽?zāi)J(rèn)的外補(bǔ)...
    _Yfling閱讀 14,093評論 1 92
  • 大家都知道萬維網(wǎng)的應(yīng)用層使用了HTTP協(xié)議,并且用瀏覽器作為入口訪問網(wǎng)絡(luò)上的資源。用戶在使用瀏覽器訪問一個(gè)網(wǎng)站時(shí)需...
    SylvanasSun閱讀 2,232評論 1 12
  • (注1:如果有問題歡迎留言探討,一起學(xué)習(xí)!轉(zhuǎn)載請注明出處,喜歡可以點(diǎn)個(gè)贊哦?。ㄗ?:更多內(nèi)容請查看我的目錄[ht...
    love丁酥酥閱讀 1,521評論 0 4
  • 轉(zhuǎn)載 前言 見解有限,如有描述不當(dāng)之處,請幫忙及時(shí)指出,如有錯(cuò)誤,會(huì)及時(shí)修正。 ----------超長文+多圖預(yù)...
    程序員之路閱讀 1,183評論 3 21

友情鏈接更多精彩內(nèi)容