注解:JavaScript 是一種輕量級(jí)的編程語言,它可以在網(wǎng)頁上實(shí)現(xiàn)復(fù)雜的功能,廣泛用于客戶端Web開發(fā)。無論你在電腦上,手機(jī)上,還是其他設(shè)備上瀏覽的所有網(wǎng)頁,以及無數(shù)基于HTML5的手機(jī)App,交互邏輯都是由JavaScript驅(qū)動(dòng)的。
無論當(dāng)前 JavaScript 代碼是內(nèi)嵌還是在外鏈文件中,頁面的下載和渲染都必須停下來等待腳本執(zhí)行完成。JavaScript 執(zhí)行過程耗時(shí)越久,瀏覽器等待響應(yīng)用戶輸入的時(shí)間就越長。瀏覽器在下載和執(zhí)行腳本時(shí)出現(xiàn)阻塞的原因在于,腳本可能會(huì)改變頁面或 JavaScript 的命名空間,它們對(duì)后面頁面內(nèi)容造成影響。一個(gè)典型的例子就是在頁面中使用document.write()。
JavaScript 代碼內(nèi)嵌示例
當(dāng)瀏覽器遇到< script>標(biāo)簽時(shí),當(dāng)前 html 頁面無從獲知 JavaScript 是否會(huì)向< p>標(biāo)簽添加內(nèi)容,或引入其他元素,或甚至移除該標(biāo)簽。因此,這時(shí)瀏覽器會(huì)停止處理頁面,先執(zhí)行 JavaScript代碼,然后再繼續(xù)解析和渲染頁面。同樣的情況也發(fā)生在使用 src 屬性加載 JavaScript的過程中,瀏覽器必須先花時(shí)間下載外鏈文件中的代碼,然后解析并執(zhí)行它。在這個(gè)過程中,頁面渲染和用戶交互完全被阻塞了。
腳本位置
HTML 4 規(guī)范指出 < script> 標(biāo)簽可以放在 HTML 文檔的< head>或< body>中,并允許出現(xiàn)多次。web 開發(fā)人員一般習(xí)慣在 < head> 中加載外鏈的 JavaScript,接著用 < link> 標(biāo)簽用來加載外鏈的 CSS 文件或者其他頁面信息。
低效率腳本位置示例。
然而這種常規(guī)的做法卻隱藏著嚴(yán)重的性能問題。在清單 2 的示例中,當(dāng)瀏覽器解析到 < script> 標(biāo)簽(第 4 行)時(shí),瀏覽器會(huì)停止解析其后的內(nèi)容,而優(yōu)先下載腳本文件,并執(zhí)行其中的代碼,這意味著,其后的 styles.css 樣式文件和< body>標(biāo)簽都無法被加載,由于< body>標(biāo)簽無法被加載,那么頁面自然就無法渲染了。因此在該 JavaScript 代碼完全執(zhí)行完之前,頁面都是一片空白。
由于腳本會(huì)阻塞頁面其他資源的下載,因此推薦將所有< script>標(biāo)簽盡可能放到< body>標(biāo)簽的底部,以盡量減少對(duì)整個(gè)頁面下載的影響。
推薦的代碼放置位置示例
這段代碼展示了在 HTML 文檔中放置< script>標(biāo)簽的推薦位置。盡管腳本下載會(huì)阻塞另一個(gè)腳本,但是頁面的大部分內(nèi)容都已經(jīng)下載完成并顯示給了用戶,因此頁面下載不會(huì)顯得太慢。這是優(yōu)化 JavaScript 的首要規(guī)則:將腳本放在底部。
組織腳本
由于每個(gè)< script>標(biāo)簽初始下載時(shí)都會(huì)阻塞頁面渲染,所以減少頁面包含的< script>標(biāo)簽數(shù)量有助于改善這一情況。這不僅針對(duì)外鏈腳本,內(nèi)嵌腳本的數(shù)量同樣也要限制。瀏覽器在解析 HTML 頁面的過程中每遇到一個(gè)< script>標(biāo)簽,都會(huì)因執(zhí)行腳本而導(dǎo)致一定的延時(shí),因此最小化延遲時(shí)間將會(huì)明顯改善頁面的總體性能。
這個(gè)問題在處理外鏈 JavaScript 文件時(shí)略有不同。考慮到 HTTP 請求會(huì)帶來額外的性能開銷,因此下載單個(gè) 100Kb 的文件將比下載 5 個(gè) 20Kb 的文件更快。也就是說,減少頁面中外鏈腳本的數(shù)量將會(huì)改善性能。
通常一個(gè)大型網(wǎng)站或應(yīng)用需要依賴數(shù)個(gè) JavaScript 文件。您可以把多個(gè)文件合并成一個(gè),這樣只需要引用一個(gè)< script>標(biāo)簽,就可以減少性能消耗。文件合并的工作可通過離線的打包工具或者一些實(shí)時(shí)的在線服務(wù)來實(shí)現(xiàn)。
需要特別提醒的是,把一段內(nèi)嵌腳本放在引用外鏈樣式表的< link>之后會(huì)導(dǎo)致頁面阻塞去等待樣式表的下載。這樣做是為了確保內(nèi)嵌腳本在執(zhí)行時(shí)能獲得最精確的樣式信息。因此,建議不要把內(nèi)嵌腳本緊跟在< link>標(biāo)簽后面。
無阻塞的腳本
減少 JavaScript 文件大小并限制 HTTP 請求數(shù)在功能豐富的 Web 應(yīng)用或大型網(wǎng)站上并不總是可行。Web 應(yīng)用的功能越豐富,所需要的 JavaScript 代碼就越多,盡管下載單個(gè)較大的 JavaScript 文件只產(chǎn)生一次 HTTP 請求,卻會(huì)鎖死瀏覽器的一大段時(shí)間。為避免這種情況,需要通過一些特定的技術(shù)向頁面中逐步加載 JavaScript 文件,這樣做在某種程度上來說不會(huì)阻塞瀏覽器。
無阻塞腳本的秘訣在于,在頁面加載完成后才加載 JavaScript 代碼。這就意味著在 window 對(duì)象的 onload事件觸發(fā)后再下載腳本。有多種方式可以實(shí)現(xiàn)這一效果。
延遲加載腳本
HTML 4 為< script>標(biāo)簽定義了一個(gè)擴(kuò)展屬性:defer。Defer 屬性指明本元素所含的腳本不會(huì)修改 DOM,因此代碼能安全地延遲執(zhí)行。defer 屬性只被 IE 4 和 Firefox 3.5 更高版本的瀏覽器所支持,所以它不是一個(gè)理想的跨瀏覽器解決方案。在其他瀏覽器中,defer 屬性會(huì)被直接忽略,因此< script>標(biāo)簽會(huì)以默認(rèn)的方式處理,也就是說會(huì)造成阻塞。然而,如果您的目標(biāo)瀏覽器支持的話,這仍然是個(gè)有用的解決方案。
defer 屬性使用方法示例

帶有 defer 屬性的< script>標(biāo)簽可以放置在文檔的任何位置。對(duì)應(yīng)的 JavaScript 文件將在頁面解析到< script>標(biāo)簽時(shí)開始下載,但不會(huì)執(zhí)行,直到 DOM 加載完成,即onload事件觸發(fā)前才會(huì)被執(zhí)行。當(dāng)一個(gè)帶有 defer 屬性的 JavaScript 文件下載時(shí),它不會(huì)阻塞瀏覽器的其他進(jìn)程,因此這類文件可以與其他資源文件一起并行下載。
任何帶有 defer 屬性的< script>元素在 DOM 完成加載之前都不會(huì)被執(zhí)行,無論內(nèi)嵌或者是外鏈腳本都是如此。清單 5 的例子展示了defer屬性如何影響腳本行為:
defer 屬性對(duì)腳本行為的影響
這段代碼在頁面處理過程中彈出三次對(duì)話框。不支持 defer 屬性的瀏覽器的彈出順序是:“defer”、“script”、“l(fā)oad”。而在支持 defer 屬性的瀏覽器上,彈出的順序則是:“script”、“defer”、“l(fā)oad”。請注意,帶有 defer 屬性的< script>元素不是跟在第二個(gè)后面執(zhí)行,而是在 onload 事件被觸發(fā)前被調(diào)用。
如果您的目標(biāo)瀏覽器只包括 Internet Explorer 和 Firefox 3.5,那么 defer 腳本確實(shí)有用。如果您需要支持跨領(lǐng)域的多種瀏覽器,那么還有更一致的實(shí)現(xiàn)方式。
HTML 5 為< script>標(biāo)簽定義了一個(gè)新的擴(kuò)展屬性:async。它的作用和 defer 一樣,能夠異步地加載和執(zhí)行腳本,不因?yàn)榧虞d腳本而阻塞頁面的加載。但是有一點(diǎn)需要注意,在有 async 的情況下,JavaScript 腳本一旦下載好了就會(huì)執(zhí)行,所以很有可能不是按照原本的順序來執(zhí)行的。如果 JavaScript 腳本前后有依賴性,使用 async 就很有可能出現(xiàn)錯(cuò)誤。
掃一掃 加微信咨詢