“CSS文件在header中引入,JS文件在body底部引入”,這條建議在前端界幾乎是黃金法則。
首先,筆者聲明,堅決支持這條法則!
只不過,在現(xiàn)代瀏覽器中,尤其是Chrome,已經(jīng)對瀏覽器渲染做了極大的優(yōu)化。哪怕并未遵從這條規(guī)矩,恐怕也不會引發(fā)太大的性能問題。
(注:下面提到的瀏覽器僅限于chrome)
1. 誤區(qū):瀏覽器在解析完整個HTML才會渲染頁面
其實,“為達到更好的用戶體驗,render引擎會力求盡快將內(nèi)容顯示在屏幕上。它不必等到整個 HTML 文檔解析完畢之后,就會開始構(gòu)建呈現(xiàn)樹和設置布局。在不斷接收和處理來自網(wǎng)絡的其余內(nèi)容的同時,render引擎會將部分內(nèi)容解析并顯示出來”(來自文章《瀏覽器的工作原理:新式網(wǎng)絡瀏覽器幕后揭秘》By Tali Garsiel and Paul Irish)。
一般,前端主要關(guān)心首屏(也就是可視區(qū)域內(nèi)的頁面)渲染速度,這也是,為什么要提倡“圖片懶加載”的原因。
2. 誤區(qū):按照順序依次下載CSS和JS文件
其實外部腳本文件和CSS文件是并行下載的。筆者做了個小實驗,如下。
HTML部分:
<html>
<head>
<link rel="stylesheet" href="css.css">
</head>
<body>
...
<script type="application/javascript" src="js1.js"></script>
<script type="application/javascript" src="js2.js"></script>
</body>
</html>
腳本和CSS加載情況:

一般資源(如圖片,CSS文件)的獲取和加載不會阻擋當前webkit的渲染過程,但是,某些資源會阻礙主線程渲染(如JS文件),這時,webkit會啟動另外一個線程去遍歷后面的HTML,并收集需要的資源URL,發(fā)起請求。因此,Chrome支持并發(fā)下載資源文件(參考《WebKit技術(shù)內(nèi)幕-朱永盛》)。
有文章將此現(xiàn)象稱為“瀏覽器預解析”:瀏覽器先對HTML代碼做靜態(tài)分析找到外鏈的JS和CSS文件,然后并行下載(但是執(zhí)行順序不變)。PS:IE>=8 及其他主流瀏覽器基本都實現(xiàn)了這個功能。
注意:針對JS文件,并行下載完成后,有序執(zhí)行。
小貼士:
Chrome瀏覽器渲染過程步驟如下(簡單分析):
1. 解析HTML構(gòu)建DOM樹,同時下載腳本,CSS和圖片;
2. CSS文件下載好之后構(gòu)建CSSOM樹;
3. DOM tree和CSSOM tree合并生成Render tree;
4. 做重排(layout)和重繪(paint)工作;
3.小結(jié)
為了提高性能,真正需要關(guān)心的是“首次必須加載哪些CSS和JS?”,盡可能通過異步方式加載那些并不是首屏必需的外部文件。并且,盡量不要加載那些會頻繁操作DOM節(jié)點定位或樣式(尤其是首屏的DOM元素)的JS文件,否則,會導致頁面做多次重排和重繪工作,影響渲染速度。
那么,如何異步加載腳本或者CSS文件?
加載事件一般在window.onload事件中觸發(fā),這時,頁面已經(jīng)完全呈現(xiàn)(相應的資源文件下載完畢),只需要動態(tài)創(chuàng)建<script>標簽或者<link>標簽即可,如下(注意:動態(tài)加載文件也可以利用瀏覽器緩存):
window.onload = function(){
downloadCSS("a.css");
downloadJS("b.js");
}
//動態(tài)加載CSS文件
function downloadCSS(url) {
var elem = document.createElement("link");
elem.rel = "stylesheet";
elem.type = "text/css";
elem.href = url;
document.body.appendChild(elem);
}
//動態(tài)加載JS文件
function downloadJS(url) {
var elem = document.createElement("script");
elem.src = url;
document.body.appendChild(elem);
}