避免CSS對DOM的阻塞
1. 異步加載CSS:
通過將CSS文件的加載設置為異步,可以確保HTML解析不會被阻塞。這可以通過在`<link>`標籤中添加`rel="async"`屬性來實現。這樣,瀏覽器會在後台加載CSS文件,而不會停止HTML的解析。
<linkrel="stylesheet"href="styles.css"defer>
2. 內聯CSS或內聯樣式:
將CSS代碼直接寫在HTML文件中,而不是通過外部文件引用,可以避免網絡請求造成的延遲。但是,這會增加HTML文件的大小,可能導致其他性能問題。
<head>
<style>
.body { font-family: Arial, sans-serif; }
.header { background-color: #eee; }
</style>
</head>
<divstyle="color: blue; font-size: 14px;">這是一段文本。</div>
這不是一個好的策略,這會使的頁面雜亂無章。
3. 使用CSS-in-JS庫:
一些庫,如Styled Components或Emotion,允許你在JavaScript中編寫CSS。這種方法可以動態生成樣式,但也可能增加JavaScript的複雜性。
使用 JavaScript 動態創建 <link> 或 <style> 標籤並插入到文檔中,這樣可以更精細地控制樣式的加載時機。
document.addEventListener("DOMContentLoaded", function() {
var link = document.createElement("link");
link.rel = "stylesheet";
link.href = "styles.css";
document.head.appendChild(link);
});
4. 優化 CSS 文件:
合併多個 CSS 文件,減少 HTTP 請求數量。同時,壓縮 CSS 代碼,減少文件大小,加快下載速度。
5. 利用媒體查詢:
通過媒體查詢,你可以根據設備的特性(如屏幕大小、分辨率等)加載不同的CSS文件。這樣,用户只會下載並應用他們真正需要的樣式。
<linkhref="style.css"rel="stylesheet">
<linkhref="style.css"rel="stylesheet"media="all">
<linkhref="portrait.css"rel="stylesheet"media="orientation:portrait">
<linkhref="print.css"rel="stylesheet"media="print">
- 第一個聲明阻塞渲染,適用於所有情況。
- 第二個聲明同樣阻塞渲染:“all”是默認類型,如果您不指定任何類型,則隱式設置為“all”。因此,第一個聲明和第二個聲明實際上是等效的。
- 第三個聲明具有動態媒體查詢,將在網頁加載時計算。根據網頁加載時設備的方向,portrait.css 可能阻塞渲染,也可能不阻塞渲染。
- 最後一個聲明只在打印網頁時應用,因此網頁首次在瀏覽器中加載時,它不會阻塞渲染。
6. 預加載和預獲取:
使用`<link rel="preload">`和`<link rel="prefetch">`可以告訴瀏覽器提前加載CSS文件。雖然這並不能阻止CSS阻塞,但它可以確保文件在需要時立即可用。
此外,值得注意的是,現代瀏覽器通常具有一些優化機制,如並行下載、緩存等,這些都可以幫助減少CSS加載對頁面加載的影響。
避免JS對DOM解析的阻塞
1. 在文檔體最後引入JS代碼
利用瀏覽器按順序解析文檔並執行腳本代碼的特點,將JS相關代碼放在HTML文檔體的最後(</body>之前),這樣當整個文檔解析完畢後才會加載並執行 JS 腳本。如果 JS 腳本間有相互依賴關係,則引入時要注意順序。
2. 利用加載事件
為了在HTML文檔內容加載完畢後才執行 JS 腳本,可以利用document的DOMContentLoaded事件或window的load事件。利用這兩個事件可以在加載 JS 腳本內容後不立即執行,而是等事件觸發時才執行,所以 JS 腳本需要放在對應的事件處理函數中。(注意,腳本的加載依然會阻塞 script 標籤後的 DOM 解析)
以上兩個加載事件是有一點區別的:DOMContentLoaded事件在 DOM 加載完成後就會觸發(不用等待頁面渲染完畢),而load是在與頁面所有依賴資源加載完畢後觸發(建議使用DOMContentLoaded,這樣在 DOM 加載完畢後就可以執行JS代碼了,不必等待其他內容的加載)
利用以上兩個事件,就不需要擔心腳本的執行會阻塞 DOM 解析了(腳本的加載仍然會阻塞在其後的 DOM 解析)
<!DOCTYPE html>
<htmllang="en">
<head>
<title>利用加載事件引入JS代碼</title>
<!-- 引入js代碼 -->
<script>
document.addEventListener('DOMContentLoaded', function() {
// JS 代碼
})
</script>
<!-- 引入外部JS文件 -->
<scriptsrc="src.js"></script>
</head>
<body>
<!-- HTML文檔內容 -->
</body>
</html>
3. 利用<script>的async和defer屬性
async和defer屬性的使用只適用於對外部JS文件的引入。
- async
使用async屬性時可告知瀏覽器在遇到<script>元素時不要中斷後續 DOM 解析(異步加載),加載完畢後會立刻執行,執行時會阻塞 DOM 解析。
<!DOCTYPE html>
<htmllang="en">
<head>
<title>利用async屬性</title>
<!-- 引入外部JS文件 -->
<scriptsrc="xxx.js"async></script>
</head>
<body>
</body>
</html>
總結:
- 優點:
當使用async屬性導入 JS 文件時不需要特地等待 DOM 解析完畢(不像上述的兩種方法——在文檔底部引入和利用加載事件引入),DOM 的解析和 JS 的加載並行,這能提高網站的性能,這個優點在一些有大量JS代碼需要加載的大型網站上體現得更明顯(這也是 async 屬性誕生的初衷)
- 侷限:
只適用於外部引入的JS文件;使用async會使JS異步加載,使JS代碼的加載和執行的順序不定,這樣當多個JS文件之間有相互依賴的關係時,可能會導致代碼執行出錯;JS 代碼的執行會阻塞 DOM 解析,此時腳本不應該有 DOM 操作
- defer
defer可以延遲外部 JS 腳本到整個頁面解析完畢之後才執行。腳本也是異步加載的,但當有多個script標籤使用了defer,HTML5 規範要求腳本應該按照他們的出現順序執行,並且都會在DOMContentLoaded事件之前執行。
<!DOCTYPE html>
<htmllang="en">
<head>
<metacharset="UTF-8">
<metahttp-equiv="X-UA-Compatible"cnotallow="IE=edge">
<metaname="viewport"cnotallow="width=device-width, initial-scale=1.0">
<title>利用defer屬性</title>
<!-- 按順序引入JS -->
<scriptsrc="aaa.js"defer></script>
<scriptsrc="bbb.js"defer></script>
<scriptsrc="ccc.js"defer></script>
</head>
<body>
</body>
</html>
總結:
- 優點:
腳本在下載時不會阻塞頁面的解析,這意味着頁面的渲染可以繼續進行,而不需要等待腳本下載完成。
由於頁面渲染不需要等待腳本下載和執行,因此可以減少頁面的總加載時間,提高用户體驗。
- 侷限:
腳本在文檔完全解析後才會執行,這意味着它們可能在 DOMContentLoaded 事件觸發之前或之後執行,這可能會影響腳本中某些依賴於 DOM 元素的操作;
對於動態添加的腳本(例如通過 JavaScript 的 document.createElement 方法創建的腳本標籤),defer 屬性不會生效;
由於 defer 腳本在頁面解析完成後才執行,如果腳本包含用户界面的初始化代碼,可能會稍微延遲用户與頁面的交互。
選用async和defer的策略:
1. 如果腳本無需等待頁面解析,且無依賴獨立運行,那麼應使用 async;
2. 如果腳本需要等待頁面解析,且依賴於其它腳本,調用這些腳本時應使用 defer,將關聯的腳本按所需順序引入
貼一下外國網站:https://www.growingwiththeweb.com/2014/02/async-vs-defer-attributes.html
4. 動態腳本元素
<script>
var script=document.createElement('script');
script.src='./index.js';
document.getElementsByTagName('head')[0].appendChild(script);
</script>
....
總結:
- 優點:
無論何時啓動下載,文件(index.js)的下載和執行過程都不會阻塞頁面的渲染;
- 缺點:
使用動態注入,在index.js腳本中使用document.write()不能得到想要的結果
5. XMLHttpRequest腳本注入
var xhr = new XMLHttpRequest();
xhr.open('GET','./file1.js',true);
xhr.onreadystatechange=function(){
if(xhr.readyState==4){
if(xhr.status >= 200 && xhr.status < 300 || xhr.status == 304){
var script = document.createElement('script');
script.type='text/javascript';
script.text='xhr.responseText';
document.body.appendChild(script);
}
}
};
總結:
- 優點:
可以下載JavaScript代碼但不立即執行,由於代碼是在<script>標籤之外返回的,因此它下載後不會自動執行,這使得可以把腳本的執行推行到準備好的時候;
同樣的代碼在所有主流瀏覽器中都能正常工作
- 缺點:
比較繁瑣