該把JS文件放在HTML文檔的那個位置

首發於:技術風暴

我們今天來聊一聊關於JavaScript文件的引入位置的問題;大家在平時的Web開發中有沒有想過這樣一個問題,那就是我應該在文檔的頭部(也就是<head>標籤內部裡面)引入所需要的JavaScript文件還是應該在尾部(也就是</body>之前)引入所需要的JavaScript文件呢?今天我們就來深入的探究一下這個問題。

首先我們需要了解的一點就是,在瀏覽器渲染頁面之前,它需要通過解析HTML標記然後構建DOM樹。在這個過程中,如果解析器遇到了一個腳本(script),它就會停下來,並且執行這個腳本,然後才會繼續解析HTML。如果遇到了一個引用外部資源的腳本(script),它就必須停下來等待這個腳本資源的下載,而這個行為會導致一個或者多個的網路往返,並且會延遲頁面的首次渲染時間。

還有一點是需要我們注意的,那就是外部引入的腳本(script)會阻塞瀏覽器的並行下載,HTTP/1.1規範表明,瀏覽器在每個主機下並行下載的組件不超過兩個(也就是說,瀏覽器一次只能夠同時從同一個伺服器載入兩個腳本);如果你網站的圖片是通過多個伺服器提供的,那麼按道理來說,你的網站可以一次並行下載多張圖片。但是,當我們網站在載入腳本的時候;瀏覽器不會再啟動任何其它的下載,即使這些組件來自不同的伺服器。

看到這裡,也許很多開發者都會想:既然把腳本(script)資源放在head裡面是個不好的主意,並且可能會阻塞瀏覽器渲染頁面;那我們是不是要把所有的JavaScript文件都放置到文檔的底部呢?這個當然也是太過極端了,因為還是有一些情況需要我們在頭部引用腳本的;到底是哪些情況需要我們這麼做呢,下面我們來看看一些大公司的做法:

我們可以看到,這個搜索頁面在頭部載入了5個JavaScript文件(箭頭標註的地方),其中兩個JavaScript文件是內聯的(inline),另外三個大家可以看到script標籤上都添加了async屬性,那這個屬性是幹嘛的呢?我們會在下面解釋。

我們可以看到,百度也在頭部引入了一些JavaScript文件,這些文件引入的方式與Google的做法差不多,都在引入外部資源的script標籤上添加了async屬性,除了第一個JavaScript文件沒有那樣做。

最後一個是facebook的首頁,令我比較出乎意料的是,facebook的首頁的頭部引入了大量的腳本(script),大家可以看一下截圖

不過基本上facebook的script標籤上面都添加了async屬性,下面我們先來來說一下script標籤上面這個async屬性的作用。

這個屬性是HTML5給script新添加的屬性,而且只適用於外部的JavaScript文件,如果在script標籤上添加了這個屬性,那麼表明這個腳本資源就不再是同步載入的了,而是非同步載入的,所以不會阻塞瀏覽器對頁面的渲染。當然這個屬性會存在一些兼容性問題,一些瀏覽器還未實現對這個屬性的支持。

我們可以看到,雖然這些網站大部分的script標籤(針對引入的外部文件)都添加了async屬性,但是還是有一些script標籤沒有添加async屬性,那就表示這些資源是同步載入執行的,在這裡你可能會問,那這些資源為什麼不使用非同步載入呢?原因很大程度上是因為,這些腳本需要在瀏覽器渲染頁面之前就執行的;比如Yahoo在Best Practices for Speeding Up Your Web Site中就指出,如果你的腳本中使用了document.write在頁面中插入內容的話,那就不能夠將這條腳本放置到文檔的底部了。類似的還有weibo,weibo的head中也使用了一個要在頁面渲染之前就執行的腳本,如下:

<script type="text/javascript">n try {document.execCommand("BackgroundImageCache", false, true);n } catch (e) {}n</script>n

還有百度首頁的head中也有兩條需要在頁面渲染之前就執行的JavaScript文件:

<script data-compress="strip">nfunction h(obj){n obj.style.behavior=url(#default#homepage);n var a = obj.setHomePage(//www.baidu.com/);n}n</script>n<script>window._ASYNC_START=new Date().getTime();</script>n

還有一些比如Google和Baidu他們搜索頁面同步載入的那些JavaScrip文件一些是為了在頁面渲染之前做一些全局的處理(比如Google)添加了全局變數google。

還有的就是單純的滿足自己業務上的一些需求了,比如百度同步載入的那個JavaScript文件:

所以說,除了上面這些情況外,其它的情況下我們的腳本資源都需要放在文檔的底部;當然這裡還有一些需要我們注意的問題,首先,腳本載入的順序很重要,比如如果你的腳本需要使用jQuery庫,那麼你就應該在載入你的腳本之前先載入jQuery庫。其次,有些腳本是需要等到某些元素載入完成之後才可以執行的,那麼你可以將你的腳本緊挨在那個元素的後面;還有一些元素是通過腳本動態創建的,所以它們也需要放在合適的位置。比如微博的:

如果使用過一些框架的腳手架你就會發現,這些框架打包後的那個index.html裡面引入的外部JavaScript資源都是放在文檔的底部的,並且它們也是按照順序來的,vendor.js文件(項目使用的框架,庫打包形成的文件)先引入,然後才是app.js文件(我們寫的代碼文件打包形成的),這就說明了引入腳本文件的順序也是很重要的。

到現在為止,我們已經討論了很多關於把JavaScript文件放在文檔的頭部還是尾部的原因,那麼下面我們可以總結出一些載入JavaScript文件的最佳實踐;

  • 對於必須要在DOM載入之前運行的JavaScript腳本,我們需要把這些腳本放置在頁面的head中,而不是通過外部引用的方式,因為外部的引用增加了網路的請求次數;並且我們要確保內斂的這些JavaScript腳本是很小的,最好是壓縮過的,並且執行的速度很快,不會造成瀏覽器渲染的阻塞。

  • 對於支持使用script標籤的async和defer屬性的瀏覽器,我們可以使用這兩個屬性;其中需要注意的點就是,async表示的意思是非同步載入JavaScript文件,它的下載過程可以在HTML的解析過程中進行,載入完成之後立即執行這個文件的代碼,執行文件代碼的過程中會阻塞HTML的解析,它不保證文件載入的順序。defer表示的意思是在HTML文檔解析之後在執行載入完成的JavaScript文件,JavaScript文件的下載過程可以在HTML的解析過程中進行,它是按照script標籤的先後順序來載入文件的。更多詳細的解釋可以參考async vs defer attributes

到這裡為止,整篇文章就算是結束了,如果你還想進一步的了解關於JavaScript文件載入的一些知識,可以看看這篇文章

插播一個提問我就想知道,知乎為什麼不添加使用Markdown編輯答案和寫文章的功能? - 知乎,提問好幾天,木有人回答,不知道大家怎麼看待這個問題的?

參考的一些資料:

  • Remove Render-Blocking JavaScript

  • Put Scripts at the Bottom

  • Best Practice: Where to include your script tags

  • Script Element

  • execCommand

  • What is difference between using JavaScript in head and body?

  • Where To Include JavaScript Files In A Document

  • Should I always put my JavaScript file in the head tag of my HTML file so that the code is loaded at the start?

  • Why put JavaScript in head

  • Deep dive into the murky waters of script loading

  • Asynchronous and deferred JavaScript execution explained

推薦閱讀:

解密Angular WebWorker Renderer (一)
2D圓形隨機分布
興趣部落的前端性能優化實踐概覽
精讀《2017前端性能優化備忘錄》
如何不擇手段提升scroll事件的性能

TAG:前端开发 | 前端性能优化 |