讀古今文學網 > 編寫高質量代碼:改善JavaScript程序的188個建議 > 建議173:編寫無阻塞JavaScript腳本 >

建議173:編寫無阻塞JavaScript腳本

管理JavaScript代碼是一個棘手的問題,因為執行代碼阻塞了其他處理過程,如用戶界面繪製。當JavaScript引擎遇到<script>標籤時,頁面必須停下來等待下載和執行代碼(如果是外部的),然後再繼續處理頁面其他部分。但是,有幾種方法可以減少這種操作對性能的影響。

❑將所有<script>標籤放置在頁面的底部,緊靠body關閉標籤</body>的上方,這樣可以保證頁面在腳本運行之前完成解析。

❑將腳本分組打包。頁面的<script>標籤越少,頁面的加載速度就越快,響應也更加迅速。不論外部腳本文件,還是內聯代碼都應該如此。

也可以使用非阻塞方式下載JavaScript。

❑為<script>標籤添加defer屬性(只適用於IE和Firefox 3.5以上版本)。

❑動態創建<script>元素,並且用它下載並執行代碼。

❑用XHR對像下載代碼,並且將其注入到頁面中。

通過使用上述策略,可以極大地提高那些大量使用JavaScript代碼的網頁性能。

JavaScript在瀏覽器中的性能,是開發者所要面對的最重要的可用性問題。此問題因JavaScript的阻塞特徵而變得複雜。大多數瀏覽器使用單進程處理UI更新和JavaScript運行等多個任務,並且同一時間只能執行一個任務。當JavaScript運行時而其他的事情不能被瀏覽器處理時,JavaScript運行了多長時間,在瀏覽器空閒之後響應用戶輸入之前的等待時間就有多長。

因此,<script>標籤的出現使整個頁面因腳本解析和運行而出現等待。不論實際的JavaScript代碼是內聯的還是包含在一個不相干的外部文件中的,頁面下載和解析過程必須停下,等待腳本完成這些處理才能繼續。這是頁面生命週期必不可少的部分,因為腳本可能在運行過程中修改頁面內容。

例如,最典型的是document.write函數。


<html>

<head>

<title>Script Example</title>

</head>

<body>

<p>

<script type=\"text/javascript\">

document.write((new Date).toDateString);

</script>

</p>

</body>

</html>


當瀏覽器遇到一個<script>標籤時,無法預知JavaScript是否在<p>標籤中添加內容,因此,瀏覽器會停下來,運行此JavaScript代碼,然後再繼續解析和翻譯頁面。同樣的事情也發生在使用src屬性加載JavaScript的過程中,瀏覽器必須首先下載外部文件的代碼,然後解析並運行此代碼。這個過程會佔用一些時間,使頁面解析和用戶交互完全阻塞。

<script>標籤用於加載外部JavaScript文件,該標籤可以放在HTML文檔的<head>或<body>標籤中,可以在其中多次出現。除此類代碼外,<head>部分還包含<link>標籤用於加載外部CSS文件等。因此,最好把樣式和行為所依賴的部分放在一起,先加載它們,使頁面可以得到正確的外觀和行為,例如:


<html>

<head>

<title></title>

<script type=\"text/javascript\"src=\"file1.js\"></script>

<script type=\"text/javascript\"src=\"file2.js\"></script>

<script type=\"text/javascript\"src=\"file3.js\"></script>

<link rel=\"stylesheet\"type=\"text/css\"href=\"styles.css\">

</head>

<body>

<p></p>

</body>

</html>


雖然這些代碼看起來是正確的,但是它們確實存在性能問題:在<head>部分加載了3個JavaScript文件。由於每個<script>標籤阻塞了頁面的解析過程,因此直到它完整地下載並運行了外部JavaScript代碼之後,頁面處理才能繼續進行。如果腳本文本很大,用戶會察覺到延遲。

瀏覽器在解析<body>標籤之前,不會渲染頁面的任何部分。用這種方法把腳本放到頁面的頂端,將導致一個可以察覺的延遲:在頁面打開之前,顯示為一幅空白的頁面,此時用戶既不能閱讀,也不能與頁面進行交互操作。

在上面代碼中,第一個JavaScript文件開始下載並阻塞了其他文件的下載過程。接下來,在file1.js下載完之後和file2.js開始下載之前有一個延時,這是file1.js完全運行所需的時間。每個文件必須等待前一個文件下載完成並運行完之後,才能開始自己的下載過程。當這些文件正在下載時,用戶面對一個空白的屏幕。這是大多數瀏覽器的行為模式。

一般讀者希望<script>標籤正在下載外部資源時,不必阻塞其他<script>標籤。不幸的是,JavaScript的下載仍然要阻塞其他資源(比如圖片)的下載過程。即使腳本之間的下載過程互不阻塞,頁面仍要等待所有JavaScript代碼下載並執行完之後才能繼續。因此,在瀏覽器允許並行下載提高性能之後,該問題並沒有完全解決。腳本阻塞仍然是一個問題。

因為腳本阻塞其他頁面資源的下載過程,所以推薦的辦法為:將所有<script>標籤放在盡可能接近<body>標籤底部的位置,盡量減少對整個頁面下載的影響。


<html>

<head>

<title></title>

<link rel=\"stylesheet\"type=\"text/css\"href=\"styles.css\">

</head>

<body>

<p></p>

<script type=\"text/javascript\"src=\"file1.js\"></script>

<script type=\"text/javascript\"src=\"file2.js\"></script>

<script type=\"text/javascript\"src=\"file3.js\"></script>

</body>

</html>


上面代碼展示了<script>標籤在HTML文件中的推薦位置。儘管腳本下載之間互相阻塞,不過頁面已經下載完成並顯示在用戶面前了,進入頁面的速度不會太慢。

由於每個<script>標籤下載時會阻塞頁面解析過程,因此限制頁面的<script>總數也可以改善性能。這個規則對內聯腳本和外部腳本同樣適用。每當頁面解析碰到一個<script>標籤時,緊接著有一段時間用於代碼執行。最小化這些延遲時間可以改善頁面的整體性能。

這個問題與外部JavaScript文件處理過程略有不同。每個HTTP請求都會產生額外的性能負擔,下載一個100KB的文件比下載4個25KB的文件要快。總之,減少引用外部腳本文件的數量可以降低性能損耗。在一個大型網站或網頁應用需要多次請求JavaScript文件時,可以將這些文件整合成一個文件,只需要一個<script>標籤引用,就可以減少性能損失。