一個頁面從服務器訪問回來后,瀏覽器拿到頁面源代碼,之后做的一些事情(1-5是交給GUI渲染線程完成的)
- 生成 DOM TREE =>對HTML的處理
- 基于HTTP獲取的是流文件(進制編碼)
- 把進制編碼編譯為具體的字符
- 詞法解析
- 生層具體的節點(元素節點/文本節點...)
- 按照相互的嵌套關系生成一個DOM樹(節點樹)
- 生成 DOM TREE =>對HTML的處理
- 生成 CSSOM TREE =>對CSS的處理
- 生成 RENDER TREE (渲染樹)
- 布局/回流/重排 Layout
- 按照渲染樹計算出每一個元素在視口中的位置和大小
- 布局/回流/重排 Layout
- 分層
- 按照計算出來的樣式進行分層
- 單獨計算每一層的繪制列表(具體怎么繪制)
- 分層
- 6.繪制/重繪 Painting
- 把生成的繪制列表提交給“合成線程”
- “合成線程”進行我們最后的繪制,呈現在瀏覽器的頁面上
真正的同時做多件事情必須依賴多線程(瀏覽器是多線程的)
- JS本身是單線程的 (因為瀏覽器本身只分配一個線程 “GUI渲染線程” 運行JS代碼),從本質來講是不能同時做多件事情的,但有同步跟異步
- 同步:一件事情完成,再去做下一件事情
- 異步:上一件事情沒有完成(做一些特殊處理[async await等]),下一件事情繼續執行 (但不是JS可以同時處理兩個事情)
- 瀏覽器生層DOM TREE/CSSOM TREE...的過程也是單線程的“GUI渲染線程”(配合瀏覽器的多線程去完成一些事情,例如:資源請求就是利用的就是瀏覽器的HTTP網絡線程去做的)
瀏覽器具體的解析過程 “GUI渲染線程”:
- 自上而下解析完所有的HTML標簽/各種節點后,DOM TREE就生成了
- 但是過程中還會遇到一些比較特殊的
- link href='xxx.css' 外鏈式- >瀏覽器會分配一個新的HTTP網絡線程去加載資源文件,不會阻礙DOM樹的渲染
-
<style> ... </style> 內嵌式- >不用去請求新的資源文件了,但是此時樣式還沒有處理,瀏覽器會做一個記錄,它會等待所有的CSS資源加載回來之后,按照先后順序依次渲染CSS,從而生成CSSOM樹
-** @import 'xxx.css'** 導入式 - >也是分配網絡HTTP線程去加載資源文件,但是此時GUI渲染線程會被阻塞掉[阻礙DOM樹渲染](只有等資源加載回來,才會繼續渲染DOM) - 遇到script 內嵌js代碼的 - >立即執行JS(阻礙DOM TREE的渲染)
- 遇到script 外鏈js代碼的(src)- > 阻礙DOM TREE的渲染,同時分配一個HTTP線程去加載資源文件,加載回來后立即執行JS,如果JS中沒有采用異步,直接獲取DOM元素,而DOM元素此時還沒有渲染,JS是獲取不到的
- 遇到img - >某些老舊版本瀏覽器會阻礙DOM渲染,新版瀏覽器雖然不會阻礙DOM渲染,但是圖片資源的請求會占用HTTP線程(瀏覽器同時只能開6~7個HTTP線程,這樣圖片/音視頻資源加載本來就會慢一些,會影響其他資源link/script等的加載)
可做的性能優化
- link放到HEAD中(盡可能提前去加載資源文件,這樣等DOM樹渲染完,資源可能也加載回來了); 當代瀏覽器的機制越發完善,Webkit瀏覽器預測解析:chrome的預加載掃描器html-preload-scanner通過掃描節點中的 “src” , “link”等屬性,找到外部連接資源后進行預加載,避免了資源加載的等待時間,同樣實現了提前加載以及加載和執行分離
- 把script放到頁面的底部(先渲染DOM TREE,再執行JS,也可以獲取到DOM元素了),也可以基于事件DOMContentLoaded/load等到結構加載完再去獲取DOM元素
- 當代前端開發,開始只把首屏的結構/內容寫出來,渲染只是首屏的,當首屏加載完,頁面滾動的時候,再基于JS創建其他屏幕的結構和內容 => 骨架屏/SSR =>客戶端骨架屏,開始首屏結構都沒有,只有一個loading或者占位圖而已...