性能優(yōu)化之window.onload
前言
最近在做一些性能優(yōu)化相關的工作,相信大家在工作過程中也會遇到一些性能優(yōu)化相關的場景,這對于前端開發(fā)者來講是一項加分技能。為了我們的用戶在使用我們的產(chǎn)品時能夠有一個非常好的體驗,我們需要對頁面進行診斷優(yōu)化。在行業(yè)中,我們的頁面P90在兩秒內(nèi)算是達標,超過這個時間那么你就可能會流失部分用戶。
TIP:P90指的是頁面性能數(shù)據(jù)從小到大排序,在90%位置的數(shù)據(jù)。
比如:P90為兩秒,那它的意思就是90%的用戶都能夠在「兩秒內(nèi)」打開頁面
對于性能優(yōu)化內(nèi)容可能比較多,我們這里就先著重了解window.onload相關內(nèi)容。對于頁面加載時長,我們就避免不了涉及window.onload。
性能分析
做性能優(yōu)化肯定免不了需要對頁面性能進行分析,我們一般會使用ChromeDevTool作為基礎的性能分析工具,觀察頁面性能情況
Network:觀察網(wǎng)絡資源加載耗時及順序
Performace:觀察頁面渲染表現(xiàn)及JS執(zhí)行情況
Lighthouse:對網(wǎng)站進行整體評分,找出可優(yōu)化項
今天我們先著重來看Network的相關內(nèi)容,比如我們打開瀏覽器控制臺:
這里我們可以看到這兩項數(shù)據(jù):DOMContentLoaded時間為841ms、Load時間為2.06s。
它倆分別對應兩個事件:
DOMContentLoaded
當初始的 「HTML」 文檔被完全加載和解析完成之后,**DOMContentLoaded** 事件被觸發(fā),而無需等待樣式表、圖像和子框架的完全加載。
Load
「load」 事件在整個頁面及所有依賴資源如樣式表和圖片都已完成加載時觸發(fā)。它與 DOMContentLoaded 不同,后者只要頁面 DOM 加載完成就觸發(fā),無需等待依賴資源的加載。
看完兩者的解釋之后,相信大家應該明白了為什么Load花的時間要比DOMContentLoaded長了吧
因為load事件會被大量媒體資源阻塞,瀏覽器只有在它認為頁面上的所有資源都加載完成了才會觸發(fā)load事件。
兩者的區(qū)別
1.DOM完整的解析過程:
- 解析HTML
- 加載外部腳本與樣式文件
- 解析并執(zhí)行腳本
- DOM樹構建(DOMContentLoaded事件觸發(fā))
- 加載圖片等資源
- 頁面加載完畢(Load事件觸發(fā))
2.DOM的解析受JS加載和執(zhí)行的影響,我們在優(yōu)化時應盡量對JS進行壓縮、拆分處理(HTTP2下),能減少 DOMContentLoaded 時間
3.圖片、視頻、CSS等資源,會阻塞 onload 事件的觸發(fā),我們在優(yōu)化過程中需要優(yōu)化資源的加載時機,讓load事件盡快觸發(fā)
深入理解window.onload
onload觸發(fā)時機
JS 加載并執(zhí)行完畢且頁面中所有外鏈資源加載完成之后大約 3 - 4ms(這個值跟機型和瀏覽器有關)。
比如:
window.onload = () => {
console.log('load')
}
setTimeout(() => {
console.log('timeout')
}, 3)
結果是setTimeout先執(zhí)行,這里把值改的稍大一點你會發(fā)現(xiàn)就是load先執(zhí)行了
哪些因素會影響window.onload
JS執(zhí)行
window.onload = () => {
console.log('load')
}
for(let i = 0; i < 100000; i++) {
console.log(i)
}
當我們寫了一個非常耗時的JS任務時,你會發(fā)現(xiàn)DOMContentLoaded與Load事件都會等很久才會觸發(fā)。
說明JS的執(zhí)行不僅會阻塞DOMContentLoaded事件的觸發(fā),也會阻塞Load事件的觸發(fā)。所以在優(yōu)化過程中,JS的執(zhí)行也是一個重點關注對象。
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的觸發(fā)。
關于async的解釋MDN上是這樣說的:
對于普通腳本,如果存在 async 屬性,那么普通腳本會被并行請求,并盡快解析和執(zhí)行。
對于模塊腳本,如果存在 async 屬性,那么腳本及其所有依賴都會在延緩隊列中執(zhí)行,因此它們會被并行請求,并盡快解析和執(zhí)行。
該屬性能夠消除「解析阻塞的 Javascript」。解析阻塞的 Javascript 會導致瀏覽器必須加載并且執(zhí)行腳本,之后才能繼續(xù)解析。
這里可能會有誤解,我覺得它應該是不會阻塞其它腳本內(nèi)容的加載與執(zhí)行,由于它的加載是在load之前的,所以它依然會阻塞load的觸發(fā),但從整體上來看,它對性能優(yōu)化還是有幫助的。
defer異步加載腳本
這里還是跟上面一樣的場景,我們把async換成defer。
這里看上去跟async的加載沒什么不同,它的加載依然會比正常加載的方式滯后,但會在load之前。
關于defer:
- 這個布爾屬性的設置是為了向瀏覽器表明,該腳本是要在文檔被解析后,但在觸發(fā) DOMContentLoaded 事件之前執(zhí)行的。
- 包含 defer 屬性的腳本將阻塞 DOMContentLoaded 事件觸發(fā),直到腳本完成加載并執(zhí)行。
- 包含 defer 屬性的腳本會按照它們出現(xiàn)在文檔中的順序執(zhí)行。
- 這個屬性能夠消除「阻塞解析的 JavaScript」,在這種情況下,瀏覽器必須在繼續(xù)解析之前加載和執(zhí)行腳本。
所以這里跟上面差不多,對性能優(yōu)化也是有幫助的,需要注意使用場景。
圖片預加載
在工作過程中我們可能會有一些圖片預加載的使用場景,主要是為了能夠讓一些較大的圖片資源能夠快速的渲染呈現(xiàn)給用戶,我們一般會提前加載一次圖片,等到真正使用時瀏覽器就可以直接從緩存中取出并渲染。
<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中預加載了一張圖片,雖然這張圖片并沒有真實渲染,但它也是會發(fā)起請求的,并影響load事件的觸發(fā)。
所以我們在做預加載時也需要考慮給頁面性能帶來的影響。
影響load時間執(zhí)行的內(nèi)容還有很多,在對頁面進行性能優(yōu)化時,這些內(nèi)容都是可以進行優(yōu)化方向
onload與native
我們都知道H5頁面在通過native得webview容器進行渲染時,頂部都會有一個加載進度條,有時候在弱網(wǎng)環(huán)境下,這個進度條會一直在那慢慢加載,很長時間不會消失,非常影響用戶體驗,這最主要的原因是onload 的觸發(fā)被阻塞,從而客戶端控制的進度條不會消失,頁面調(diào)用客戶端的方法不會執(zhí)行。
iOS 中判斷 webview 加載完成的 「webViewDidFinishLoad」 方法,Android 中判斷 webview 加載完成的 「onPageFinished」 方法本質觸發(fā)時機上都對應頁面上的 window.onload,一般來說會稍晚于 window.onload(某些特殊情況會早于 window.onload,比如頁面里有 iframe 等情況)。
也就是說 「對 onload 有影響的因素也同樣會影響這些 Native 方法」。而在 Hybrid 開發(fā)中,一些 Native 和 Web 之間的交互和調(diào)用往往要在webViewDidFinishLoad / onPageFinished 之后。因此如果 onload 的觸發(fā)被推遲了,那么這些 Native 相關的調(diào)用也都會被推遲。
「因此如果是Hybrid應用,尤其要注意讓onload盡快觸發(fā)。」
performance性能統(tǒng)計
DOMContentLoaded事件與Load事件花費的時間,我們可以通過performance 這個對象的一些屬性進行統(tǒng)計,時間精確到納秒級。很多公司的性能監(jiān)控平臺也主要是利用這個對象的數(shù)據(jù)進行上報的。
- connectStart:HTTP(TCP)開始建立連接的時間。如果是持久連接,則和 fetchStart 的時間相等,注意,如果在傳輸層發(fā)生了錯誤且重新建立連接,這里顯示的是新建立連接的開始時間。
- connectEnd: 完成建立連接的時間。
- domComplete:DOM 樹解析完成,并且資源準備就緒的時間,Document.readyState 變?yōu)?complete,并將拋出 readystatechange 相關事件。
- domContentLoadedEventEnd:DOM 解析完成后,網(wǎng)頁內(nèi)資源加載完成的時間(如 JS、css 加載執(zhí)行完畢)。
- domContentLoadedEventStart:DOM 解析完成后,網(wǎng)頁內(nèi)資源加載開始的時間在 DOMContentLoaded 事件拋出前發(fā)生。
- loadEventStart:load 事件觸發(fā),也即 load 回調(diào)函數(shù)開始執(zhí)行的時間。注意:如果沒有綁定 load 事件,值為 0。
- loadEventEnd:load 事件的回調(diào)函數(shù)執(zhí)行完畢的時間。
- 等...更詳細內(nèi)容可查看MDN文檔