JavaScript 文件加載與阻塞問題之性能優化案例詳解
上來先給一個問題:在書寫html頁面時,當你要從外部引入js文件時,script標簽會放置在哪個位置呢,放置位置不同對頁面加載有影響嗎?
默認情況下,瀏覽器是同步加載 JavaScript 腳本:即渲染引擎遇到 script 標簽就會停下來,等到執行完腳本,再繼續向下渲染。如果是外部腳本,還必須加入腳本下載的時間。
如果腳本體積很大,下載和執行的時間就會很長,因此造成瀏覽器堵塞,用戶會感覺到瀏覽器“卡死”,出現短暫的空白,沒有任何響應。這會造成很不好的用戶體驗,解決這個問題有兩種方案:
①. 改變script標簽的放置位置。最好將其丟在body標簽的最後面,即放在</ body>標簽的前面,這種方式不會影響瀏覽器的DOM渲染,會讓頁面處理執行完後才去執行它
②. 同步轉異步。瀏覽器允許腳本異步加載,這種方式可以讓 script 標簽繼續放在head頭部,下面是兩種異步加載的語法:
<script src="./1.js" async></script> <script src="./1.js" defer></script>
async與defer
異步就是 script 標簽打開defer或async屬性,腳本就會異步加載。瀏覽器渲染引擎遇到這一行命令,就會開始下載外部腳本,在下載的同時渲染引擎會直接執行後面的命令。
async和defer都會讓外部腳本下載時,渲染引擎不停下來。
async屬性與defer的區別就在於:
藍色線代表網絡讀取(腳本下載),紅色線代表執行,這倆都是針對腳本的;綠色線代表 HTML 解析。
defer屬性,瀏覽器會立即下載相應的腳本,在下載的過程中頁面的處理不會停止,等到文檔解析完成後腳本才會執行。
async屬性,瀏覽器會立即下載相應的腳本,在下載的過程中頁面的處理不會停止,下載完成後立即執行,執行過程中頁面處理會停止。
若不設置任何屬性,那麼當與到script腳本時,會等script腳本下載和執行都完成之後才繼續執行下面的頁面處理。
【註】且async比defer更“厲害”,當同一標簽同時使用兩種屬性時,遵循async!!!
多個腳本
async和defer的區別不僅體現在外部script文件的下載和執行上,更體現在多個腳本存在時的不同:
先來個代碼示例:
外部script文件
1.js 文件:
// ... 非常多的js代碼 console.log('1');
2.js 文件:
console.log('2');
主html文件
使用defer:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>js阻塞</title> <!-- defer會讓dom先執行 --> <script src="./1.js" defer></script> <script src="./2.js" defer></script> </head> <body> <h1>js的阻塞是如何進行的?</h1> <script> document.addEventListener('DOMContentLoaded', function() { console.log('DOMContentLoaded'); }) </script> </body> </html>
控制臺執行結果:
使用async:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>js阻塞</title> <!-- defer會讓dom先執行 --> <script src="./1.js" async></script> <script src="./2.js" async></script> </head> <body> <h1>js的阻塞是如何進行的?</h1> <script> document.addEventListener('DOMContentLoaded', function() { console.log('DOMContentLoaded'); }) </script> </body> </html>
控制臺執行結果:
從控制臺的運行結果就可以看出:
defer:第一個延遲腳本會先於第二個延遲腳本執行(當然,如果一些瀏覽器不完全遵照html5標準,也會出現不按順序執行的情況),且這兩個腳本會先於DOMContentLoaded事件執行。
async:哪個先下載完成哪個就立即執行!!!這兩個腳本不一定會在DOMContentLoaded事件觸發之前執行,但一定會在window.onload 事件之前執行完成。另外,值得註意的是,在先下載完的腳本執行過程中,其他腳本不會停止下載,而會繼續下載。
【註】DOMContentLoaded會在dom加載完成後觸發,即文檔完全加載和解析之後觸發。
○ 更多關於DOMContentLoaded,請參考 https://www.jb51.net/article/222345.htm
小結
- 防堵塞的最佳實踐就是將script標簽放到body的最下面;
- defer 和 async 在網絡讀取(下載)方面是一樣的,都是異步的(相較於 HTML 解析)
它倆的差別在於腳本下載完之後何時執行,顯然 defer 是最接近我們對於應用腳本加載和執行的要求的,尤其對於有script文件之間有依賴的情況(依賴就是可能這個js文件引用瞭上一個js文件的內容),它是按照加載順序執行腳本的!!! - async 則是一個亂序執行的主,對它來說腳本的加載和執行是緊緊挨著的,所以不管你聲明的順序如何,隻要它加載完瞭就會立刻執行
仔細想想,async 對於應用腳本的用處不大,因為它完全不考慮依賴(哪怕是最低級的順序執行),不過它對於那些可以不依賴任何腳本或不被任何腳本依賴的腳本來說卻是非常合適的,最典型的例子:Google Analytics - defer/async都隻適合與操作外部腳本,另外,在操作DOM腳本時最好不要使用async/defer,因為比如使用async時可能頁面還沒加載完,就執行瞭js代碼,這樣很可能產生異常;如果一定要使用,可以把需要操作 DOM 的js 部分放在 DOMContentLoaded 事件回調中執行☺!
參考
[1] https://blog.csdn.net/mx18519142864/article/details/82021754
[2] https://blog.csdn.net/weixin_42561383/article/details/86564715
[3] https://segmentfault.com/q/1010000000640869
到此這篇關於JavaScript 文件加載與阻塞問題之性能優化案例詳解的文章就介紹到這瞭,更多相關JavaScript 文件加載與阻塞問題內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- js與css的阻塞問題詳析
- JavaScript之BOM構成和常用事件詳解
- JavaScript DOMContentLoaded事件案例詳解
- 在頁面加載之後執行JavaScript
- JavaScript基礎介紹與實例