1. 影響瀏覽器渲染方式的文檔模式
每個瀏覽器都有自己的頁面渲染引擎蔑滓。渲染引擎包括兩部分:一部分負責 HTML郊酒、CSS 代碼的解析(渲染引擎遇绞,如 blink 引擎 or 內(nèi)核),一部分負責 JavaScript 代碼的解析(JavaScript 引擎燎窘,如 V8 引擎)摹闽。瀏覽器的渲染模式(以何種文檔模式進行渲染)主要對 CSS 解析有影響(也對腳本有一些影響)。不同的渲染模式褐健,在 CSS 解析上存在差異付鹿,比如對盒模型的處理。
不同的渲染模式是歷史遺留問題造成的蚜迅。早期 W3C 沒統(tǒng)一標準舵匾,瀏覽器生產(chǎn)商自己決定頁面如何渲染;標準出來后谁不,現(xiàn)有瀏覽器渲染肯定存在與標準不同的地方坐梯。為了兼容,就出現(xiàn)了兩種瀏覽器渲染模式(IE 最早提出)刹帕,正統(tǒng)叫法為文檔模式(Document Mode)(如果文檔是按照標準編寫的吵血,瀏覽器采用標準渲染模式;如果文檔并沒有按照標準編寫偷溺,那么瀏覽器以怪異模式渲染)蹋辅。此外,還有第三種模式—近標準模式挫掏。那么瀏覽器又該如何知道文檔有沒有按照標準編寫呢晕翠?實際上瀏覽器在渲染頁面之前會檢查兩個內(nèi)容,一個是頁面是否有 DOCTYPE
信息砍濒,另外一個是頁面是否有 x-ua-compatible
信息淋肾。DOCTYPE
告訴瀏覽器:我的文檔是哪種模式,你確定后爸邢,就按這種模式渲染樊卓;如果沒有這個頭信息,瀏覽器就按怪異模式渲染杠河。x-ua-compatible
是 IE8 的一個專有<meta>
屬性碌尔,可以指定瀏覽器以怎樣的模式進行渲染。
注意:瀏覽器模式和瀏覽器渲染模式是兩個概念券敌,前者可以理解為IE瀏覽器中特有的概念唾戚,后者在本文中就是指文檔模式。
2. HTML 文檔的解析過程
當用戶在瀏覽器鍵入某網(wǎng)站地址待诅,網(wǎng)站首頁文檔 index.html
加載完成后叹坦,瀏覽器開始解析 HTML。下文根據(jù)不同的 HTML 資源結(jié)構(gòu)分析解析過程卑雁。
2.1 純 HTML 文檔募书,無 CSS 和腳本
如果 HTML 文檔中只有 HTML绪囱,沒有 CSS 和腳本的話,問題極其簡單莹捡。瀏覽器解析 HTML鬼吵,構(gòu)建 DOM 樹,DOM 樹構(gòu)建完成后(觸發(fā) DOMContentLoaded
事件)篮赢,構(gòu)建 render 樹齿椅,接著布局和繪制像素。
2.2 包含內(nèi)聯(lián)樣式和內(nèi)聯(lián)腳本的 HTML 文檔
如果 HTML 文檔中存在內(nèi)聯(lián)樣式和腳本启泣,這個時候涣脚,問題變得稍微復(fù)雜一些。瀏覽器解析 HTML种远,構(gòu)建 DOM 樹涩澡,當解析到<style>
標簽時顽耳,樣式信息開始被解析坠敷,CSSOM 被構(gòu)建,但是它并不會影響到 HTML 的解析和 DOM 樹的構(gòu)建射富。當 HTML 解析到<script>
標簽時膝迎,因為腳本有可能改變 DOM 內(nèi)容,所以 HTML 的解析必須等到腳本執(zhí)行完畢后再繼續(xù)胰耗。腳本又有可能操作 CSSOM 限次,所以腳本必須等到 CSS 解析完畢后才能執(zhí)行。確保此刻 CSS 解析完成柴灯,腳本被交到 JS 引擎手里卖漫,由 JS 引擎執(zhí)行。當腳本執(zhí)行完畢赠群,HTML 繼續(xù)解析羊始,直到全部 HTML 解析完畢,DOM 樹構(gòu)建完成(觸發(fā) DOMContentLoaded
事件)查描。
注意:DOMContentLoaded
事件只和 HTML 的加載和解析有關(guān)突委,一旦 HTML 解析完成,這個事件就會被觸發(fā)冬三,不管此時還有沒有CSS的解析匀油、圖片的下載或者異步腳本的加載和執(zhí)行。DOM 樹一旦構(gòu)建完成勾笆,就會開始構(gòu)建 render 樹敌蚜,并不管 CSS 是否解析完畢。如果構(gòu)建 render 樹的時候窝爪,CSS 還沒有解析完成钝侠,那么 render 樹會用占位符代替應(yīng)該有的 CSSOM 節(jié)點该园,當該節(jié)點加載解析好后,再重新計算樣式帅韧。
但是同步腳本的執(zhí)行會阻塞 HTML 的解析里初,從而會影響到 DOMContentLoaded
事件的觸發(fā)。同時又要注意忽舟,CSS 會阻塞 JS 腳本的執(zhí)行双妨,從而間接影響到 HTML 的解析和 DOMContentLoaded
事件的觸發(fā)。
2.3 包含外部 CSS 和腳本的 HTML 文檔
如果 HTML 文檔中存在外聯(lián)樣式表和腳本叮阅,問題變得更復(fù)雜一點刁品。HTML 文檔加載完成后,瀏覽器首先掃描 HTML 文檔,查看有哪些外部資源需要啟動 network operation 來請求資源,并在 HTML 解析的同時零聚,發(fā)送所有的請求察纯。CSS 資源加載完畢后,會立即開始解析構(gòu)建 CSSOM替久。(同步腳本加載完畢后,并不能立刻執(zhí)行。)當 HTML 解析到<script>
標簽拌汇,先確認腳本加載完畢了沒,如果沒弊决,那得等噪舀;如果加載好了,還得看 CSS 解析好了沒飘诗。如果沒与倡,那還得等;如果 CSS 解析好了昆稿,那就能把腳本交給 JS 引擎去執(zhí)行了纺座。當 JS 執(zhí)行完畢,HTML 繼續(xù)解析貌嫡,DOM 繼續(xù)構(gòu)建比驻,直到全部構(gòu)建完成,DOMContentLoaded
事件被觸發(fā)岛抄。緊接著别惦,就是構(gòu)建 render 樹。
如果腳本有async
屬性夫椭,問題就又不一樣了掸掸。async
屬性默認該腳本不會影響到 DOM 內(nèi)容,所以只要腳本下載完成,(相關(guān))CSS 解析完畢扰付,腳本立刻執(zhí)行堤撵,不用等著 HTML 解析到<script>
標簽再開始執(zhí)行。同樣羽莺,HTML 也不會等著腳本執(zhí)行完畢再解析实昨。仿佛兩者看不到對方,只管做自己的事情就行了盐固。
3. JS 解釋器的工作原理
上文提到瀏覽器在解析 HTML 文檔的時候荒给,會把腳本交給 JS 引擎執(zhí)行,那么 JS 引擎是如何執(zhí)行腳本(evaluating script)的呢刁卜?
3.1 掃描全局變量志电,確定所有已聲明的變量或函數(shù)名
你如果利用 chrome 控制臺調(diào)試 JS 代碼,這個過程是看不到的蛔趴,但確實存在挑辆。JS 解釋器對腳本進行全局掃描,結(jié)束后得到全局環(huán)境中的變量對象孝情,此過程發(fā)生了變量聲明提升和函數(shù)聲明提升鱼蝉。所有變量都沒被賦值,其值為 undefined
咧叭;函數(shù)聲明提升還包括了函數(shù)體的提升蚀乔。下圖是個例子:
備注:你可能會好奇上面這個圖是怎么回事烁竭,這里簡要概述一下:黃色區(qū)塊表示JS執(zhí)行環(huán)境菲茬,白色表格代表變量對象鍵值對模型,表格左列為全局變量(變量對象中的key)派撕,說明當前全局環(huán)境中共有model
婉弹、octopus
、catView
和catListView
四個變量终吼。
以后會專門寫一篇文章介紹JS執(zhí)行時的內(nèi)存模型镀赌,幫助大家形象理解JS代碼的運行機制,從而有助于理解作用域际跪、執(zhí)行環(huán)境商佛、this指向、閉包姆打、繼承良姆、原型鏈等抽象概念。
3.2 順序執(zhí)行所有語句
當 JS 解釋器知道整個文件中都有哪些聲明好的全局變量或函數(shù)后幔戏,就會開始順序執(zhí)行文件中的語句玛追,當然是從第一行開始。如果是賦值語句,就執(zhí)行賦值操作痊剖;如果是函數(shù)調(diào)用語句韩玩,就執(zhí)行函數(shù)調(diào)用。
下圖是 debugger 剛開始時變量的情況陆馁,很明顯找颓,剛剛被 JS 解釋器點過名,還沒有開始執(zhí)行賦值操作叮贩。
當解釋器移動到下一行代碼時叮雳,這個變量也就被賦值,存儲了數(shù)據(jù)妇汗。在本例中這個數(shù)據(jù)是個對象類型帘不,有兩個屬性,其中一個是數(shù)組杨箭,另一個是空值寞焙。
腳本的最后是函數(shù)調(diào)用語句:
JS 解析器執(zhí)行到這里,準備調(diào)用 octopus
的 init
方法互婿。
當所有的語句執(zhí)行完畢后捣郊,JS 解釋器任務(wù)結(jié)束,主導(dǎo)權(quán)交到 HTML 解析器手中慈参,瀏覽器繼續(xù)解析 HTML 文檔呛牲。
從上述過程,我們能看出瀏覽器解析渲染 HTML 文檔是單線程的驮配,除了發(fā)送外部資源請求的操作娘扩。
4. 總結(jié)
瀏覽器的工作原理是網(wǎng)站性能優(yōu)化的基礎(chǔ)知識。CSS 不會阻塞 HTML 的解析壮锻,但是會阻塞渲染琐旁,CSS 的解析會阻塞腳本的執(zhí)行,而腳本會阻塞 HTML 的解析猜绣。