性能優化之window.onload

前言

最近在做一些性能優化相關的工作,相信大家在工作過程中也會遇到一些性能優化相關的場景,這對於前端開發者來講是一項加分技能。爲了我們的用戶在使用我們的產品時能夠有一個非常好的體驗,我們需要對頁面進行診斷優化。在行業中,我們的頁面P90在兩秒內算是達標,超過這個時間那麼你就可能會流失部分用戶。

TIP:P90指的是頁面性能數據從小到大排序,在90%位置的數據。

比如:P90爲兩秒,那它的意思就是90%的用戶都能夠在兩秒內打開頁面

對於性能優化內容可能比較多,我們這裏就先着重瞭解window.onload相關內容。對於頁面加載時長,我們就避免不了涉及window.onload

性能分析

做性能優化肯定免不了需要對頁面性能進行分析,我們一般會使用ChromeDevTool作爲基礎的性能分析工具,觀察頁面性能情況

Network:觀察網絡資源加載耗時及順序

Performace:觀察頁面渲染表現及JS執行情況

Lighthouse:對網站進行整體評分,找出可優化項

今天我們先着重來看Network的相關內容,比如我們打開瀏覽器控制檯:

load-1.png

這裏我們可以看到這兩項數據:DOMContentLoaded時間爲841ms、Load時間爲2.06s

它倆分別對應兩個事件:

DOMContentLoaded

當初始的 HTML 文檔被完全加載和解析完成之後,DOMContentLoaded 事件被觸發,而無需等待樣式表、圖像和子框架的完全加載。

Load

load 事件在整個頁面及所有依賴資源如樣式表和圖片都已完成加載時觸發。它與 DOMContentLoaded 不同,後者只要頁面 DOM 加載完成就觸發,無需等待依賴資源的加載。

看完兩者的解釋之後,相信大家應該明白了爲什麼Load花的時間要比DOMContentLoaded長了吧

因爲load事件會被大量媒體資源阻塞,瀏覽器只有在它認爲頁面上的所有資源都加載完成了纔會觸發load事件。

兩者的區別

  • DOM完整的解析過程:
    • 解析HTML
    • 加載外部腳本與樣式文件
    • 解析並執行腳本
    • DOM樹構建(DOMContentLoaded事件觸發)
    • 加載圖片等資源
    • 頁面加載完畢(Load事件觸發)
  • DOM的解析受JS加載和執行的影響,我們在優化時應儘量對JS進行壓縮、拆分處理(HTTP2下),能減少 DOMContentLoaded 時間
  • 圖片、視頻、CSS等資源,會阻塞 onload 事件的觸發,我們在優化過程中需要優化資源的加載時機,讓load事件儘快觸發

深入理解window.onload

onload觸發時機

JS 加載並執行完畢且頁面中所有外鏈資源加載完成之後大約 3 - 4ms(這個值跟機型和瀏覽器有關)

比如:

window.onload = () => {
  console.log('load')
}
setTimeout(() => {
  console.log('timeout')
}, 3)

結果是setTimeout先執行,這裏把值改的稍大一點你會發現就是load先執行了

load-2.png

哪些因素會影響window.onload

JS執行

window.onload = () => {
  console.log('load')
}
for(let i = 0; i < 100000; i++) {
  console.log(i)
}

當我們寫了一個非常耗時的JS任務時,你會發現DOMContentLoadedLoad事件都會等很久纔會觸發。

load-3.png

說明JS的執行不僅會阻塞DOMContentLoaded事件的觸發,也會阻塞Load事件的觸發。所以在優化過程中,JS也是一個重點關注對象。

async異步加載腳本

<script src="https://cdn.bootcdn.net/ajax/libs/vue/3.2.47/vue.cjs.js" async></script>
<script src="https://cdn.bootcdn.net/ajax/libs/Chart.js/4.2.1/chart.js"></script>

爲了對比,這裏我加載了兩個JS文件,一個使用async異步加載,一個直接加載,我們再到控制檯來查看此時的加載情況。

load-4.png

這裏我們可以看到兩個文件都是在Load之前就會加載,只不過使用了async異步加載會比正常加載的後加載,說明使用了async異步加載腳本依然會阻塞Load的觸發。

關於async的解釋MDN上是這樣說的:

對於普通腳本,如果存在 async 屬性,那麼普通腳本會被並行請求,並儘快解析和執行。

對於模塊腳本,如果存在 async 屬性,那麼腳本及其所有依賴都會在延緩隊列中執行,因此它們會被並行請求,並儘快解析和執行。

該屬性能夠消除解析阻塞的 Javascript。解析阻塞的 Javascript 會導致瀏覽器必須加載並且執行腳本,之後才能繼續解析。

這裏可能會有誤解,我覺得它應該是不會阻塞其它腳本內容的加載與執行,由於它的加載是在load之前的,所以它依然會阻塞load的觸發,但從整體上來看,它對性能優化還是有幫助的。

defer異步加載腳本

這裏還是跟上面一樣的場景,我們把async換成defer

<script src="https://cdn.bootcdn.net/ajax/libs/vue/3.2.47/vue.cjs.js" defer></script>
<script src="https://cdn.bootcdn.net/ajax/libs/Chart.js/4.2.1/chart.js"></script>

load-5.png

這裏看上去跟async的加載沒什麼不同,它的加載依然會比正常加載的方式滯後,但會在load之前。

關於defer:

這個布爾屬性的設置是爲了向瀏覽器表明,該腳本是要在文檔被解析後,但在觸發 DOMContentLoaded 事件之前執行的。

包含 defer 屬性的腳本將阻塞 DOMContentLoaded 事件觸發,直到腳本完成加載並執行。

包含 defer 屬性的腳本會按照它們出現在文檔中的順序執行。

這個屬性能夠消除阻塞解析的 JavaScript,在這種情況下,瀏覽器必須在繼續解析之前加載和執行腳本。

所以這裏跟上面差不多,對性能優化也是有幫助的,需要注意使用場景。

圖片預加載

在工作過程中我們可能會有一些圖片預加載的使用場景,主要是爲了能夠讓一些較大的圖片資源能夠快速的渲染呈現給用戶,我們一般會提前加載一次圖片,等到真正使用時瀏覽器就可以直接從緩存中取出並渲染。

<div class="container">
  <img src="https://imgservices.image.com/s06012023/9ac85415.g0q5wz.png" class="zan_icon" />
</div>

<script>
  window.onload = () => {
    console.log('load')
  }
  const img = new Image();
  img.src = 'https://router.vuejs.org/logo.svg';

</script>

比如這裏,我們在html裏面通過img加載了一張圖片,在JS中預加載了一張圖片,雖然這張圖片並沒有真實渲染,但它也是會發起請求的,並影響load事件的觸發。

load-6.png

所以我們在做預加載時也需要考慮給頁面性能帶來的影響

影響load時間執行的內容還有很多,在對頁面進行性能優化時,這些內容都是可以進行優化方向

onload與native

我們都知道H5頁面在通過native得webview容器進行渲染時,頂部都會有一個加載進度條,有時候在弱網環境下,這個進度條會一直在那慢慢加載,很長時間不會消失,非常影響用戶體驗,這最主要的原因是onload 的觸發被阻塞,從而客戶端控制的進度條不會消失,頁面調用客戶端的方法不會執行。

iOS 中判斷 webview 加載完成的 webViewDidFinishLoad 方法,Android 中判斷 webview 加載完成的 onPageFinished 方法本質觸發時機上都對應頁面上的 window.onload,一般來說會稍晚於 window.onload(某些特殊情況會早於 window.onload,比如頁面裏有 iframe 等情況)。

也就是說 對 onload 有影響的因素也同樣會影響這些 Native 方法。而在 Hybrid 開發中,一些 Native 和 Web 之間的交互和調用往往要在webViewDidFinishLoad / onPageFinished 之後。因此如果 onload 的觸發被推遲了,那麼這些 Native 相關的調用也都會被推遲。

因此如果是Hybrid應用,尤其要注意讓onload儘快觸發。

performance性能統計

DOMContentLoaded事件與Load事件花費的時間,我們可以通過performance 這個對象的一些屬性進行統計,時間精確到納秒級。很多公司的性能監控平臺也主要是利用這個對象的數據進行上報的。
load-performance.png

  • connectStart:HTTP(TCP)開始建立連接的時間。如果是持久連接,則和 fetchStart 的時間相等,注意,如果在傳輸層發生了錯誤且重新建立連接,這裏顯示的是新建立連接的開始時間。

  • connectEnd: 完成建立連接的時間。

  • domComplete:DOM 樹解析完成,並且資源準備就緒的時間,Document.readyState 變爲 complete,並將拋出 readystatechange 相關事件。

  • domContentLoadedEventEnd:DOM 解析完成後,網頁內資源加載完成的時間(如 JS、css 加載執行完畢)。

  • domContentLoadedEventStart:DOM 解析完成後,網頁內資源加載開始的時間在 DOMContentLoaded 事件拋出前發生。

  • loadEventStart:load 事件觸發,也即 load 回調函數開始執行的時間。注意:如果沒有綁定 load 事件,值爲 0。

  • loadEventEnd:load 事件的回調函數執行完畢的時間。

  • 等...更詳細內容可查看MDN文檔

如果這篇文章有幫助到你,❤️關注+點贊❤️鼓勵一下作者,關注 前端南玖 第一時間獲取最新文章~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章