效能優化之window.onload

2023-07-03 12:00:30

前言

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

TIP:P90指的是頁面效能資料從小到大排序,在90%位置的資料。

比如:P90為兩秒,那它的意思就是90%的使用者都能夠在兩秒內開啟頁面

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

效能分析

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

Network:觀察網路資源載入耗時及順序

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

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

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

這裡我們可以看到這兩項資料: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先執行了

哪些因素會影響window.onload

JS執行

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

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

說明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之前就會載入,只不過使用了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>

這裡看上去跟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時間執行的內容還有很多,在對頁面進行效能優化時,這些內容都是可以進行優化方向

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 這個物件的一些屬性進行統計,時間精確到納秒級。很多公司的效能監控平臺也主要是利用這個物件的資料進行上報的。

  • 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檔案

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