JavaScript無阻塞加載和defer、async詳解
無阻塞加載
把js放在head里,瀏覽器是怎么去執(zhí)行它的呢,是按順序加載還是并行加載呢?在舊的瀏覽器下,都是按照先后順序來加載的,這就保證了加載的js依賴不會發(fā)生問題。但是少部分新的瀏覽器已經(jīng)開始允許并行加載js了,也就是說可以同時下載js文件,但是還是按先后順序執(zhí)行文件的。
下載是異步的沒問題,但是每個javascript執(zhí)行的時候還是同步的,就是先出現(xiàn)的script標簽一定是先執(zhí)行,即使是并行下載它是最后一個下載完成的,除非標有defer的script標簽。任何javascript在執(zhí)行的時候都會中斷當前html文檔解析,自然會阻止頁面渲染。
javascript加載是不會影響已經(jīng)渲染的頁面,但是會中斷html文檔解析,瀏覽器會在javascript執(zhí)行以后決定當前文檔是否需要進行重新渲染或者文檔重排。所以即使javascript放到最后面也會使瀏覽器暫停,但不影響之前已經(jīng)解析出來的dom文檔,此時對于用戶來說是可操作的。
javascript下載完畢之后會立即執(zhí)行,所有的javascript執(zhí)行都會阻塞瀏覽器的其他行為,例如阻塞其他javascript的執(zhí)行、其他的http請求的執(zhí)行以及頁面的解析和渲染。(html文檔中外部js的下載也會阻塞瀏覽器的行為,但通過創(chuàng)建script元素動態(tài)js的下載不會,可能是認為動態(tài)的js不會改變頁面效果,所以允許資源并行下載。)
圖示動態(tài)腳本的下載
UI線程會根據(jù)頁面里資源(資源是指css文件,圖片等等)書寫的先后順序來加載資源,加載資源也就是使用http請求獲取資源,像css外部文件,html文件以及圖片等資源http請求處理完畢也就意味著資源加載結(jié)束,但是像外部的javascript文件的加載則不同,它的加載過程被分為兩步,第一步和加載css文件和圖片一樣,就是執(zhí)行一個http請求下載外部的js文件,但是javascript完成http操作后并不意味操作完畢,UI線程接著會執(zhí)行它。js腳本的下載和執(zhí)行必須是一個完整的操作,是不能被割裂的。動態(tài)js的下載不會阻塞,但執(zhí)行一定會會。
瀏覽器為了提升用戶體驗,加快UI線程的執(zhí)行是一個無法回避的問題,但是拆分js的下載和執(zhí)行是不可行的,如是乎瀏覽器換了種方式,這個方式也就是在同一個時間能下載多個資源。
將常用的,穩(wěn)定的靜態(tài)資源統(tǒng)一放在一個靜態(tài)資源服務器上,由統(tǒng)一的域名對外提供,這個域名要和主體請求的域名不一樣,原理是因為瀏覽器只通過域名來限制連接的個數(shù),如果一個頁面里有兩個不同的域的,那么并行的http請求個數(shù)也會變成兩倍。有度,對DNS解析要開銷,所以2個最佳。
將所有外部js代碼分為UI初始化代碼和其他代碼,UI初始化代碼是在頁面加載時候執(zhí)行的代碼。讓那些不會用于頁面初始化展示的js代碼的加載和執(zhí)行操作通過onload事件在瀏覽器忙指示結(jié)束后觸發(fā),即讓那些和頁面加載無關(guān)的js腳本在onload方法里執(zhí)行
無阻塞加載腳本的核心技術(shù)就是動態(tài)的創(chuàng)建script的dom節(jié)點,而且可以跨域訪問。
var script=document.createElement("script"); script.type="text/javascript"; script.src="file.js"; document.getElementsByTagName("head")[0].appendChild(script);
動態(tài)腳本元素,就是說 <script> 標簽不是寫死在HTML中的,而是由現(xiàn)有的腳本生成的,因為 <script> 標簽也是DOM元素的一種,而JavaScript是可以通過DOM API操作DOM的。動態(tài)腳本只有在新建的script元素被添加到html文檔時開始下載,下載完立即執(zhí)行。
無阻塞腳本的好處就是不會阻塞UI的執(zhí)行,也不會影響其他同步js代碼的執(zhí)行,不無阻塞腳本改變了腳本的加載順序,所以在使用無阻塞腳本時候一定要更加注意腳本之間的依賴關(guān)系,保證整個頁面的腳本都能正常執(zhí)行。
使用無阻塞腳本了,代碼置于head標簽還是html文檔底部也就無關(guān)緊要了。
頁面加載的總時間不是衡量頁面加載快捷的標準,頁面同步阻塞加載的時間才是衡量頁面加載效率的準確標準,非阻塞腳本加載可能會增加整個頁面加載的時間,但是它可以減少頁面阻塞加載的時間。
腳本的異步執(zhí)行,會產(chǎn)生前后依賴的問題。在腳本加載執(zhí)行完畢后,非ie瀏覽器會觸發(fā)該 <script> 元素的 onload 事件,ie瀏覽器下有onreadystatechange事件,我們可以將回調(diào)放到這個事件中處理。
每當瀏覽器解析到<script>標簽(無論內(nèi)嵌還是外鏈)時,瀏覽器會優(yōu)先下載、解析并執(zhí)行該標簽中的javaScript代碼,而阻塞其后所有頁面內(nèi)容的下載和渲染。(也就是說外部js的下載也會阻塞別的線程,目前有少部分瀏覽器支持并行下載js)
無阻塞加載腳本技術(shù)的核心就是:動態(tài)下載js腳本的時候,不會阻塞UI線程的執(zhí)行。動態(tài)腳本為什么不阻塞ui線程?可能是因為瀏覽器認為動態(tài)資源不會影響頁面渲染。
讓script延遲和異步的兩個屬性:defer和async
js腳本會改變文檔輸入流的內(nèi)容,所以執(zhí)行js時會暫停頁面的渲染。對于內(nèi)聯(lián)腳本沒什么問題,因為腳本和html文檔被同時加載了。但對于外部引入的腳本,腳本的下載(取決于網(wǎng)速)也會阻塞瀏覽器文檔的解析和渲染,甚至會阻塞有些瀏覽器下載別的資源(目前有些瀏覽器已經(jīng)實現(xiàn)并行下載)。所以出現(xiàn)defer和async屬性,優(yōu)化頁面的顯示。
defer(延遲)是html4.0中定義的,該屬性使得瀏覽器能延遲腳本的下載,等document文檔載入和解析完成后,按照他們在文檔中出現(xiàn)順序再去下載解析。也就是說defer屬性的<script>就類似于將<script>放在body底部的效果,會在document的DOMContentLoaded事件之前執(zhí)行。
將腳本放在body底部比給腳本增加defer屬性讓腳本延遲加載更好。
async(異步)是HTML5新增的屬性,該屬性的作用是讓瀏覽器能并行下載腳本且不阻塞瀏覽器的文檔解析和渲染,下載完成后腳本立即執(zhí)行,可能無序執(zhí)行,取決于下載完成的時間)
若瀏覽器同時支持上述兩種屬性且script標簽同時具有這兩種屬性,則async屬性會優(yōu)于defer生效。
在不支持async屬性的瀏覽器里,可以通過動態(tài)創(chuàng)建script元素并插入文檔中,實現(xiàn)腳本的異步載入和執(zhí)行:
requirejs就是使用這個方法實現(xiàn)的。
相關(guān)文章
《JavaScript高級編程》學習筆記之object和array引用類型
本文給大家分享我的javascript高級編程學習筆記之object和array引用類型,涉及到javascript引用類型相關(guān)知識,對javascript引用類型感興趣的朋友可以參考下本文2015-11-11js和jquery中循環(huán)的退出和繼續(xù)學習記錄
這篇文章主要介紹了js和jquery中循環(huán)的退出和繼續(xù)學習記錄,下哦功能不簡單,需要的朋友可以參考下2014-09-09javascript圖片切換綜合實例(循環(huán)切換、順序切換)
這篇文章主要介紹了javascript圖片切換綜合實例,包括javascript圖片循環(huán)切換、javascript圖片順序切換,兩張圖片的切換,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-01-01JavaScript設(shè)置獲取和設(shè)置屬性的方法
這篇文章主要介紹了JavaScript設(shè)置獲取和設(shè)置屬性的方法,學會使用getAttribute、setAttribute的用法,需要的朋友可以參考下2015-03-03前端獲取資源的方式(ajax、fetch)以及其區(qū)別詳解
Fetch 被稱之為下一代 Ajax 技術(shù),內(nèi)部采用 Promise 方式處理數(shù)據(jù)API 語法簡潔明了,這篇文章主要給大家介紹了關(guān)于前端獲取資源的方式(ajax、fetch)以及其區(qū)別的相關(guān)資料,需要的朋友可以參考下2024-07-07JavaScript中如何讓?x?==?1?&&?x?==?2?&&?x?==?3?等式成立
這篇文章主要介紹了JavaScript中如何讓x==1&&x==2&&x==3等式成立,文章圍繞主題展開詳細的內(nèi)容介紹,具有一定的參考價值,需要的小伙伴可以參考一下2022-07-07