該把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的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。
如果使用過一些框架的腳手架你就會發現,這些框架打包後的那個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事件的性能