發布日期:2013 年 12 月 31 日
我們可以使用 JavaScript 修改網頁的各個層面,包括內容、樣式,以及對使用者互動的回應。不過,JavaScript 也可以 區塊 DOM 建構以及在網頁顯示時延遲。放送最佳廣告 請將 JavaScript 非同步,並刪除任何不必要的 JavaScript 再從關鍵轉譯路徑擷取
摘要
- JavaScript 可以查詢及修改 DOM 和 CSSOM。
- CSSOM 上的 JavaScript 執行區塊。
- 除非明確宣告為非同步,否則 JavaScript 會禁止 DOM 建構內容。
JavaScript 是在瀏覽器中執行的動態語言,可讓我們幾乎從各個層面變更網頁行為:我們可以透過在 DOM 樹狀結構新增及移除元素來修改內容;就能修改每個元素的 CSSOM 屬性就能處理使用者輸入內容以及其他工具為了說明這一點,請查看先前的「Hello World」改為新增簡短的內嵌指令碼:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link href="style.css" rel="stylesheet" />
<title>Critical Path: Script</title>
</head>
<body>
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg" /></div>
<script>
var span = document.getElementsByTagName('span')[0];
span.textContent = 'interactive'; // change DOM text content
span.style.display = 'inline'; // change CSSOM property
// create a new element, style it, and append it to the DOM
var loadTime = document.createElement('div');
loadTime.textContent = 'You loaded this page on: ' + new Date();
loadTime.style.color = 'blue';
document.body.appendChild(loadTime);
</script>
</body>
</html>
JavaScript 可讓我們連到 DOM 並提取隱藏 span 節點的參照;節點可能不會顯示在算繪樹狀結構中,但仍位於 DOM 中。接著,取得參照後,我們可以變更文字 (透過 .textContent),甚至將計算出的顯示樣式屬性從「無」覆寫。「內嵌」即可頁面現在會顯示「Hello interactive students!」。
JavaScript 也允許我們在 DOM 中建立、設定樣式、附加及移除新元素。從技術層面來說,整個網頁只是一個大型 JavaScript 檔案,可逐一建立元素並設定其樣式。雖然這麼做可以達到目的,但在實際操作中,使用 HTML 和 CSS 會更簡單。在 JavaScript 函式的第二部分,我們會建立新的 div 元素、設定文字內容、設定樣式,然後將其附加至主體。
因此,我們修改了現有 DOM 節點的內容和 CSS 樣式,並在文件中加入全新的節點。我們的網頁不會贏得任何設計獎,但說明瞭 JavaScript 對我們的功能十分強大且靈活。
不過,雖然 JavaScript 提供許多強大的功能,但也會在網頁的轉譯方式和時間上造成許多額外的限制。
首先,請注意,在先前的範例中,內嵌指令碼位於頁面底部附近。這是因為建議您自行嘗試,但如果將指令碼移到 <span>
元素上方,就會發現指令碼失敗,並抱怨文件中任何 <span>
元素的參照。也就是說,getElementsByTagName('span')
會傳回 null
。這說明瞭一項重要屬性:Google 指令碼在文件中插入位置的時間點執行。HTML 剖析器遇到指令碼標記時,會暫停建構 DOM 並向 JavaScript 引擎產生控制權;JavaScript 引擎執行完畢後,瀏覽器接著會從上次中斷的地方繼續建構,並繼續 DOM 建構內容。
換句話說,由於指令碼區塊尚未處理,因此之後無法在網頁上找到任何元素!或是請稍微改變:執行內嵌指令碼會阻斷 DOM 建構,也會延遲初始算繪。
在頁面中加入指令碼的另一個微妙屬性是,這些指令碼不僅可以讀取及修改 DOM,還能讀取及修改 CSSOM 屬性。事實上,在本例中,我們將 span 元素的顯示屬性從「none」變更為「inline」,就是為了達到這個效果。最終結果是我們現在有競爭狀況。
如果瀏覽器尚未完成下載及建構 CSSOM,我們要如何執行指令碼?答案對效能來說不太理想:瀏覽器會延遲執行指令碼和建構 DOM,直到下載並建構 CSSOM 為止。
簡而言之,JavaScript 會在 DOM、CSSOM 和 JavaScript 執行作業之間引入許多新的依附元件。這可能會導致瀏覽器在處理及轉譯畫面上的網頁時,發生顯著的延遲:
- 文件中的指令碼位置相當重要。
- 瀏覽器遇到指令碼標記時,系統會暫停建構 DOM,直到指令碼執行完畢為止。
- JavaScript 可以查詢及修改 DOM 和 CSSOM。
- JavaScript 會暫停,直到 CSSOM 準備就緒為止。
如要大規模地「最佳化關鍵轉譯路徑」,是指解讀 HTML、CSS 和 JavaScript 之間的依附關係圖,並進行最佳化。
剖析器封鎖與非同步 JavaScript
根據預設,JavaScript 執行作業會採取「剖析器阻斷」方式:當瀏覽器在文件中遇到指令碼時,必須暫停 DOM 建構作業,將控制權交給 JavaScript 執行階段,並在繼續執行 DOM 建構作業前,讓指令碼執行。在先前範例中,我們透過內嵌指令碼看到了這個效果。事實上,內嵌指令碼一律會封鎖剖析器,除非您編寫其他程式碼來延後執行。
那麼,如果使用指令碼標記加入的指令碼呢?以上一個範例將程式碼擷取至個別檔案:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link href="style.css" rel="stylesheet" />
<title>Critical Path: Script External</title>
</head>
<body>
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg" /></div>
<script src="app.js"></script>
</body>
</html>
app.js
var span = document.getElementsByTagName('span')[0];
span.textContent = 'interactive'; // change DOM text content
span.style.display = 'inline'; // change CSSOM property
// create a new element, style it, and append it to the DOM
var loadTime = document.createElement('div');
loadTime.textContent = 'You loaded this page on: ' + new Date();
loadTime.style.color = 'blue';
document.body.appendChild(loadTime);
是否使用 <script>或內嵌 JavaScript 程式碼片段,則 兩者的行為模式會相同在這兩種情況下,瀏覽器都會暫停, 先執行指令碼,然後才處理文件的其餘部分。 不過,如果是外部 JavaScript 檔案,瀏覽器必須暫停,等待從磁碟、快取或遠端伺服器擷取指令碼,這可能會使關鍵算繪路徑延遲數十至數千毫秒。
根據預設,所有 JavaScript 都會啟用剖析器阻斷功能。由於瀏覽器不知道指令碼打算在網頁上執行的作業,因此會假設最糟的情況,並封鎖剖析器。向瀏覽器發出信號,表示不需要在所參照的某個時間點執行指令碼,瀏覽器就能繼續建構 DOM,並在指令碼準備就緒時讓指令碼在就緒後執行。例如從快取或遠端伺服器擷取檔案後。
為達成這項目標,我們將 async
屬性新增至 <script>
元素:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link href="style.css" rel="stylesheet" />
<title>Critical Path: Script Async</title>
</head>
<body>
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg" /></div>
<script src="app.js" async></script>
</body>
</html>
在指令碼標記中加入 async 關鍵字,可讓瀏覽器在等待指令碼可用時,不阻擋 DOM 建構,進而大幅提升效能。