簡單談談瀏覽器渲染頁面過程的幾個問題

瀏覽器渲染頁面,簡單的說,主要是以下幾步

  1. 解析html
  2. 構建DOM樹(第1,2一般是同時執行)
  3. 加載外部樣式表
  4. 解析外部樣式表爲CSSOM樹
  5. 將DOM樹和CSSOM樹關聯在一起形成渲染樹(render樹),有哪些節點,各個節點的CSS定義以及他們的從屬關係。
  6. 佈局,根據渲染樹計算頁面元素的大小,位置等信息
  7. 渲染,將佈局計算好的元素渲染到頁面上

幾個問題

  1. 解析html的時候,是從上到下解析的,如果遇到外部樣式表(link),會新開一個線程去下載,並不會影響DOM樹的構建,也就是說,解析html,構建DOM樹,以及加載css可以同時進行,因此將外部樣式表放到head中,越早加載越好。另外是解析完外部樣式表,纔會將DOM和CSSOM關聯再一起形成渲染樹,所以css的link如果放到頁面最下面,就算html已經解析完成,還是要等外部樣式表加載完成,纔會形成渲染樹,很大機率會浪費時間。
  2. 解析html的時候,是從上到下解析的,如果遇到外部腳本(script),會停掉解析html的工作,先加載script內容,等加載完,並且script的內容執行完畢之後,纔會繼續解析html的工作,既然解析html的工作停止了,那頁面渲染肯定不會繼續執行,客戶端用戶看到的就是一片空白,所以如果將外部腳本放到html上部,會阻塞頁面的渲染,因爲阻塞瞭解析html的工作。另外,如果script中需要操作DOM,此時DOM樹還沒有構建完成,因此找不到DOM元素,就會報錯。
  3. script的標籤上,可以添加async和defer屬性。
    ————
    defer:用於開啓新的線程下載腳本文件,並使腳本在文檔解析完成後執行。
    1)defer只適用於外聯腳本,如果script標籤沒有指定src屬性,只是內聯腳本,不要使用defer
    2)如果有多個聲明瞭defer的腳本,則會按順序下載和執行
    如果有兩個script標籤:scriptA(文件大),scriptB(文件小)都有defer屬性,A在B之前,瀏覽器併發的請求一般爲6個,因此可能A和B同時加載的情況,但是defer是按順序加載和執行的,所以就算B下載完了,也會等待A加載執行完之後,再執行。
    3)defer腳本會在DOMContentLoaded和load事件之前執行
    ————
    async:HTML5新增屬性,用於異步下載腳本文件,下載完畢立即解釋執行代碼。
    1)只適用於外聯腳本,這一點和defer一致
    2)如果有多個聲明瞭async的腳本,其下載和執行也是異步的,不能確保彼此的先後順序
    不會限制執行順序,誰先加載完,誰先執行,如果多個script之間有依賴關係,就不適用async。
    3)async會在load事件之前執行,但並不能確保與DOMContentLoaded的執行先後順序
  4. 在第五步,將DOM樹和CSSOM關聯在一起,形成渲染樹的過程,是一個非常複雜並且
    開銷很大的過程(具體過程我也沒研究過),因此DOM結構儘量簡單,css中儘量使用class和id,不要各種嵌套標籤寫太深。
  5. 重排(迴流,Reflow),第6步中,佈局計算好了頁面元素的大小,位置等信息之後,通過js等手段,修改元素的幾何屬性或者DOM節點,就要重新計算佈局。由於瀏覽器渲染界面是基於流式佈局模型的,也就是某一個DOM節點信息更改了,就需要對DOM結構進行重新計算,重新佈局界面,再次引發迴流,只是這個結構更改程度會決定周邊DOM更改範圍,即全局範圍和局部範圍,全局範圍就是從根節點html開始對整個渲染樹進行重新佈局,例如當我們改變了窗口尺寸或方向或者是修改了根元素的尺寸或者字體大小等;而局部佈局可以是對渲染樹的某部分或某一個渲染對象進行重新佈局。
    ————
    會引起重排的操作有:
    1)頁面首次渲染。
    2)瀏覽器窗口大小發生改變。
    3)元素尺寸或位置發生改變。
    4)元素內容變化(文字數量或圖片大小等等)。
    5)元素字體大小變化。
    6)添加或者刪除可見的DOM元素。
    7)激活CSS僞類(例如::hover)。
    8)設置style屬性
    9)現代瀏覽器大多都是通過隊列機制來批量更新佈局,瀏覽器會把修改操作放在隊列中,至少一個瀏覽器刷新(即16.6ms)纔會清空隊列,但當你獲取佈局信息的時候,隊列中可能有會影響這些屬性或方法返回值的操作,即使沒有,瀏覽器也會強制清空隊列,觸發迴流與重繪來確保返回正確的值。
    offsetTop、offsetLeft、offsetWidth、offsetHeight
    scrollTop、scrollLeft、scrollWidth、scrollHeight
    clientTop、clientLeft、clientWidth、clientHeight
    width、height
    getComputedStyle()
    getBoundingClientRect()
  6. 由於瀏覽器使用流式佈局,對Render Tree的計算通常只需要遍歷一次就可以完成,但table及其內部元素除外,他們可能需要多次計算,通常要花3倍於同等元素的時間,這也是爲什麼要避免使用table佈局的原因之一。
  7. 重繪(Repainting)在第7步,將元素渲染到頁面上,如果元素的樣式(color,background等)改變是,不會觸發第6步重新計算各個元素的位置大小等,只需要重新渲染頁面就可以,因此開銷要比重排小的多。
  8. 爲什麼通常在發送數據埋點請求的時候使用的是 1x1 像素的透明 gif 圖片?
    1)避免跨域(img 天然支持跨域)
    2)利用空白gif或1x1 px的img是互聯網廣告或網站監測方面常用的手段,簡單、安全、相比PNG/JPG體積小,1px 透明圖,對網頁內容的影響幾乎沒有影響,這種請求用在很多地方,比如瀏覽、點擊、熱點、心跳、ID頒發等等,
    3)圖片請求不佔用 Ajax 請求限額
    4)不會阻塞頁面加載(在第一步的時候,會和link標籤一樣,新開一個線程去加載圖片),不會影響用戶的體驗,只要new Image對象就好了,一般情況下也不需要append到DOM中,通過它的onerror和onload事件來檢測發送狀態。

參考資料:
https://www.jianshu.com/p/3be88ec81e21
http://www.imooc.com/article/45936
https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/24
https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/87

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章