這是我參與8月更文挑戰的第3天,活動詳情查看:8月更文挑戰javascript
文檔對象模型 (DOM) 會將 web 頁面與到腳本或編程語言鏈接起來。DOM模型表示具備邏輯樹的文檔。樹的每一個分支的終點都是一個節點(node),每一個節點都包含着對象(objects)。DOM的方法(methods)容許以編程方式進行訪問樹,從而改變文檔的結構,樣式和內容。節點能夠關聯上事件處理器,一旦某一事件被觸發了,那些事件處理器就會被執行。html
從網絡傳給渲染引擎的 HTML 文件字節流是沒法直接被渲染引擎理解的,因此要將其轉化爲渲染引擎可以理解的內部結構,這個結構就是 DOM。DOM 提供了對 HTML 文檔結構化的表述。在渲染引擎中,DOM 有三個層面的做用java
簡言之,DOM 是表述 HTML 的內部數據結構,它會將 Web 頁面和 JavaScript 腳本鏈接起來,並過濾一些不安全的內容node
DOM 並非一個編程語言,但若是沒有DOM, JavaScript 語言也不會有任何網頁,XML頁面以及涉及到的元素的概念或模型。在文檔中的每一個元素— 包括整個文檔,文檔頭部, 文檔中的表格,表頭,表格中的文本 — 都是文檔所屬於的文檔對象模型(DOM)的一部分,所以它們可使用DOM和一個腳本語言如 JavaScript,來訪問和處理。web
起初,JavaScript和DOM是交織在一塊兒的,但它們最終演變成了兩個獨立的實體。JavaScript能夠訪問和操做存儲在DOM中的內容,所以咱們能夠寫成這個近似的等式:編程
API (web 或 XML 頁面) = DOM + JS (腳本語言)瀏覽器
在渲染引擎內部,有一個叫HTML 解析器(HTMLParser)
的模塊,它的職責就是負責將 HTML 字節流轉換爲 DOM 結構。markdown
HTML 解析器並非等整個文檔加載完成以後再解析的,而是隨着 HTML 文檔邊加載邊解析
的,是網絡進程加載了多少數據,HTML 解析器便解析多少數據。網絡
流程:網絡進程接收到響應頭以後,會根據響應頭中的 content-type 字段來判斷文件的類型,好比 content-type 的值是「text/html」,那麼瀏覽器就會判斷這是一個 HTML 類型的文件,根據這個判斷選擇相應的解析引擎,而後爲該請求選擇或者建立一個渲染進程。渲染進程準備好以後,網絡進程和渲染進程之間會創建一個共享數據的管道,網絡進程接收到數據後就往這個管道里面放,而渲染進程則從管道的另一端不斷地讀取數據,並同時將讀取的數據傳送給 HTML 解析器。
能夠把這個管道想象成一個「水管」,網絡進程接收到的字節流像水同樣倒進這個「水管」,而「水管」的另一端是渲染進程的 HTML 解析器,它會動態接收字節流,並將其解析爲 DOM。
從圖中能夠看出,字節流轉換爲 DOM 須要三個階段。
第一個階段,經過分詞器將字節流轉換爲 Token。
解析 HTML 也是同樣的,須要經過分詞器先將字節流轉換爲一個個 Token,分爲 Tag Token 和文本 Token。將 HTML 代碼經過詞法分析生成的 Token 以下圖所示:
由圖可知,Tag Token 又分 StartTag 和 EndTag。
第二階段是將 Token 解析爲 DOM 節點
HTML 解析器維護了一個Token 棧結構,該 Token 棧主要用來計算節點之間的父子關係,在第一個階段中生成的 Token 會被按照順序壓到這個棧中。具體的處理規則以下所示:
經過分詞器產生的新 Token 就這樣不停地壓棧和出棧,整個解析過程就這樣一直持續下去,直到分詞器將全部字節流分詞完成。
第三階段是將 DOM 節點添加到 DOM 樹中
將建立的 DOM 節點,添加到 document 上,造成 DOM 樹。
HTML 解析器開始工做時,會默認建立了一個根爲 document 的空 DOM 結構,同時會將一個 StartTag document 的 Token 壓入棧底。而後通過分詞器解析出來的第一個 StartTag html Token 會被壓入到棧中,並建立一個 html 的 DOM 節點,添加到 document 上,以下圖所示
而後按照一樣的流程解析出來 StartTag body 和 StartTag div,其 Token 棧和 DOM 的狀態以下圖所示:
接下來解析出來的是第一個 div 的文本 Token,渲染引擎會爲該 Token 建立一個文本節點,並將該 Token 添加到 DOM 中,它的父節點就是當前 Token 棧頂元素對應的節點,以下圖所示:
再接下來,分詞器解析出來第一個 EndTag div,這時候 HTML 解析器會去判斷當前棧頂的元素是不是 StartTag div,若是是則從棧頂彈出 StartTag div,以下圖所示
按照一樣的規則,一路解析,最終結果以下圖所示:
經過上面的介紹,相信你已經清楚 DOM 是怎麼生成的了。不過在實際生產環境中,HTML 源文件中既包含 CSS 和 JavaScript,又包含圖片、音頻、視頻等文件,因此處理過程遠比上面這個 Demo 複雜。不過理解了這個簡單的 Demo 生成過程,咱們就能夠往下分析更加複雜的場景了。
若是頁面中含有一段 JavaScript 腳本,或者引入了腳本文件,則這段腳本的解析過程就與上面的過程有點不同了。
script標籤以前,全部的解析流程仍是和以前介紹的同樣,可是解析到script標籤時,渲染引擎判斷這是一段腳本,此時 HTML 解析器就會暫停 DOM 的解析,JavaScript 引擎介入
,由於 JavaScript 腳本可能要修改當前已經生成的 DOM 結構。
若是腳本是經過 JavaScript 文件加載的,則須要先下載這段 JavaScript 代碼
。這裏須要重點關注下載環境,由於JavaScript 文件的下載過程會阻塞 DOM 解析,而一般下載又是很是耗時的,會受到網絡環境、JavaScript 文件大小等因素的影響。
若是腳本是直接內嵌的 JavaScript 腳本,則直接執行
。
若是 JavaScript 腳本修改了 DOM 中的 div 中的內容,因此執行這段腳本以後,已經解析過的 div 節點內容也會被修改。腳本執行完成以後,HTML 解析器恢復解析過程,繼續解析後續的內容,直至生成最終的 DOM。
還有一種狀況則是,若是 JavaScript 代碼出現了,修改頁面 CSS 樣式的語句,用來操縱 CSSOM
,因此在執行 JavaScript 以前,須要先解析 JavaScript 語句之上全部的 CSS 樣式
。因此若是代碼裏引用了外部的 CSS 文件,那麼在執行 JavaScript 以前,還須要等待外部的 CSS 文件下載完成,並解析生成 CSSOM 對象以後,才能執行 JavaScript 腳本。
而 JavaScript 引擎在解析 JavaScript 代碼以前,是不知道 JavaScript 是否操縱了 CSSOM 的,因此渲染引擎在遇到 JavaScript 腳本時,無論該腳本是否操縱了 CSSOM,都會執行 CSS 文件下載,解析操做,再執行 JavaScript 腳本。因此說 JavaScript 腳本是依賴樣式表的
。
經過上面的分析,咱們知道了 JavaScript 會阻塞 DOM 生成,而樣式文件又會阻塞 JavaScript 的執行,因此在實際的工程中須要重點關注 JavaScript 文件和樣式表文件,使用不當會影響到頁面性能的。
爲防止頁面阻塞,Chrome 瀏覽器作了不少優化,其中一個主要的優化是預解析
操做。當渲染引擎收到字節流以後,會開啓一個預解析線程,用來分析 HTML 文件中包含的 JavaScript、CSS 等相關文件,解析到相關文件以後,預解析線程會提早下載這些文件。
再回到 DOM 解析上,咱們知道引入 JavaScript 線程會阻塞 DOM,不過也有一些相關的策略來規避,好比使用 CDN 來加速 JavaScript 文件的加載,壓縮 JavaScript 文件的體積。另外,若是 JavaScript 文件中沒有操做 DOM 相關代碼,就能夠將該 JavaScript 腳本設置爲異步加載,經過 async 或 defer 來標記代碼,使用方式以下所示:
<script async type="text/javascript" src='foo.js'></script>
複製代碼
<script defer type="text/javascript" src='foo.js'></script>
複製代碼
async 和 defer 雖然都是異步的,不過還有一些差別,使用 async 標誌的腳本文件一旦加載完成,會當即執行;而使用了 defer 標記的腳本文件,須要在 DOMContentLoaded 事件以前執行。
首先咱們介紹了 DOM 是如何生成的,而後又基於 DOM 的生成過程分析了 JavaScript 是如何影響到 DOM 生成的。也談到 CSS 和 JavaScript 都會影響到 DOM 的生成。
DOM生成的過程 解析 HTML 須要經過分詞器先將字節流轉換爲 Token。
若是壓入到棧中的是StartTag Token,HTML 解析器會爲該 Token 建立一個 DOM 節點,而後將該節點加入到 DOM 樹中。若是分詞器解析出來是文本 Token,那麼會生成一個文本節點,而後將該節點加入到 DOM 樹中。若是分詞器解析出來的是EndTag 標籤,HTML 解析器會查看 Token 棧頂的元素是不是 StarTag div,若是是,就將 StartTag div 從棧中彈出,表示該 div 元素解析完成。
經過分詞器產生的新 Token 就這樣不停地壓棧和出棧,整個解析過程就這樣一直持續下去,直到分詞器將全部字節流分詞完成。
在解析過程當中若是遇到 JavaScript 代碼,則中止 HTML 解析,若是js經過腳本加載的則先下載該腳本再執行,再執行以前 CSS 也會被解析生成 CSSOM。經此過程直至整個 DOM 構建完成。
文中若有錯誤,歡迎在評論區指正,若是這篇文章幫到了你,歡迎點贊👍和關注😊,但願點贊多多多多...