前端里那些你不知道的事兒之 【window.onload】

来源:https://www.cnblogs.com/jingdongkeji/archive/2023/04/07/17296711.html
-Advertisement-
Play Games

相信很多前端開發者在做項目時同時也都做過頁面性能優化,這不單是前端的必備職業技能,也是考驗一個前端基礎是否扎實的考點,而性能指標也通常是每一個開發者的績效之一。尤其馬上接近年關,頁面白屏時間是否過長、首屏載入速度是否達標、動畫是否能流暢運行,諸如此類關於性能更具體的指標和感受,很可能也是決定著年底你... ...


作者:京東科技 孫凱

一、前言

相信很多前端開發者在做項目時同時也都做過頁面性能優化,這不單是前端的必備職業技能,也是考驗一個前端基礎是否扎實的考點,而性能指標也通常是每一個開發者的績效之一。尤其馬上接近年關,頁面白屏時間是否過長、首屏載入速度是否達標、動畫是否能流暢運行,諸如此類關於性能更具體的指標和感受,很可能也是決定著年底你能拿多少年終獎回家過年的晴雨表

關於性能優化,我們一般從以下四個方面考慮:

  1. 開發時性能優化

  2. 編譯時性能優化

  3. 載入時性能優化

  4. 運行時性能優化

而本文將從第三個方面展開,講一講哪些因素將影響到頁面載入總時長,談到總時長,那總是避免不了要談及window.onload,這不但是本文的重點,也是常見頁面性能監控工具中必要的API之一,如果你對自己頁面載入的總時長不滿意,歡迎讀完本文後在評論區交流。

二、關於 window.onload

這個掛載到window上的方法,是我剛接觸前端時就掌握的技能,我記得尤為深刻,當時老師說,“對於初學者,只要在這個方法里寫邏輯,一定沒錯兒,它是整個文檔載入完畢後執行的生命周期函數”,於是從那之後,幾乎所有的練習demo,我都寫在這裡,也確實沒出過錯。

MDN上,關於onload的解釋是這樣的:load 事件在整個頁面及所有依賴資源如樣式表和圖片都已完成載入時觸發。它與DOMContentLoaded不同,後者只要頁面 DOM 載入完成就觸發,無需等待依賴資源的載入。該事件不可取消,也不會冒泡。

後來隨著前端知識的不斷擴充,這個方法後來因為有了“更先進”的DOMContentLoaded,在我的代碼里而逐漸被替代了,目前除了一些極其特殊的情況,否則我幾乎很難用到window.onload這個API,直到認識到它影響到頁面載入的整體時長指標,我才又一次拾起來它。

三、哪些因素會影響 window.onload

本章節主要會通過幾個常用的業務場景展開描述,但是有個前提,就是如何準確記錄各種類型資源載入耗時對頁面整體載入的影響,為此,有必要先介紹一下前提。

為了準確描述資源載入耗時,我在本地環境啟動了一個用於資源請求的node服務,所有的資源都會從這個服務中獲取,之所以不用遠程伺服器資源的有主要原因是,使用本地服務的資源可以在訪問的資源鏈接中設置延遲時間,如訪問腳本資源http://localhost:3010/index.js?delay=300,因鏈接中存在delay=300,即可使資源在300毫秒後返回,這樣即可準確控制每個資源載入的時間。

以下是node資源請求服務延遲相關代碼,僅僅是一個中間件:

const express = require("express")
const app = express()

app.use(function (req, res, next) {
    Number(req.query.delay) > 0
        ? setTimeout(next, req.query.delay)
        : next()
})

  • 場景一: 使用 async 非同步載入腳本場景對 onload 的影響
    示例代碼:

      <!DOCTYPE html>
      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <meta http-equiv="X-UA-Compatible" content="IE=edge">
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
          <title>test</title>
    
          <!-- 請求時長為1秒的js資源 -->
          <script src="http://localhost:3010/index.js?delay=1000" async></script>
      </head>
      <body>
      </body>
      </html>
    
    

    瀏覽器表現如下:
    alt
    通過上圖可以看到,瀑布圖中深藍色豎線表示觸發了DOMContentLoaded事件,而紅色豎線表示觸發了window.onload事件(下文中無特殊情況,不會再進行特殊標識),由圖可以得知使用了 async 屬性進行腳本的非同步載入,仍會影響頁面載入總體時長。

  • 場景二:使用 defer 非同步載入腳本場景對 onload 的影響
    示例代碼:

      <!DOCTYPE html>
      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <meta http-equiv="X-UA-Compatible" content="IE=edge">
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
          <title>test</title>
    
          <!-- 請求時長為1秒的js資源 -->
          <script src="http://localhost:3010/index.js?delay=1000" defer></script>
      </head>
      <body>
      </body>
      </html>
    
    

    瀏覽器表現如下:
    alt
    由圖可以得知使用了 defer 屬性進行腳本的非同步載入,除了正常的在DOMContentLoaded之後觸發腳本執行,也影響頁面載入總體時長。

  • 場景三:非同步腳本中再次載入腳本,也就是常見的動態載入腳本、樣式資源的情況
    html 代碼保持不變,index.js內示例代碼:

    const script = document.createElement('script')
    
    // 請求時長為0.6秒的js資源
    script.src = 'http://localhost:3010/index2.js?delay=600'
    script.onload = () => {
        console.log('js 2 非同步載入完畢')
    }
    document.body.appendChild(script)
    
    

    結果如下:
    alt
    從瀑布圖可以看出,資源的連續載入,導致了onload事件整體延後了,這也是我們再頁面中非常常見的一種操作,通常懶載入一些不重要或者首屏外的資源,其實這樣也會導致頁面整體指標的下降。

    不過值得強調的一點是,這裡有個有意思的地方,如果我們把上述代碼進行改造,刪除最後一行的document.body.appendChild(script),發現 index2 的資源請求並沒有發出,也就是說,腳本元素不向頁面中插入,腳本的請求是不會發出的,但是也會有反例,這個我們下麵再說。

    在本示例中,後來我又把腳本請求換成了 css 請求,結果是一致的。

  • 場景四:圖片的懶載入/預載入
    html 保持不變,index.js 用於載入圖片,內容如下:

    const img = document.createElement('img')
    
    // 請求時長為0.5秒的圖片資源
    img.src = 'http://localhost:3010/index.png?delay=500'
    document.body.appendChild(img)
    
    

    結果示意:
    alt
    表現是與場景三一樣的,這個不再多說,但是有意思的來了,不一樣的是,經過測試發現,哪怕刪除最後一行代碼:document.body.appendChild(img)不向頁面中插入元素,圖片也會發出請求,也同樣延長了頁面載入時長,所以部分同學就要註意了,這是一把雙刃劍:當你真的需要懶載入圖片時,可以少寫最後一行插入元素的代碼了,但是如果大量的圖片載入請求發出,哪怕不向頁面插入圖片,也真的會拖慢頁面的時長。

    趁著這個場景,再多說一句,一些埋點數據的上報,也正是藉著圖片有不需要插入dom即可發送請求的特性,實現成功上傳的。

  • 場景五:普通介面請求
    html 保持不變,index.js 內容如下:

    // 請求時長為500毫秒的請求介面
    fetch('http://localhost:3010/api?delay=500')
    
    

    結果如下圖:
    alt
    可以發現普通介面請求的發出,並不會影響頁面載入,但是我們再把場景弄複雜一些,見場景六。

  • 場景六:同時載入樣式、腳本,腳本載入完成後,內部http介面請求,等請求結果返回後,再發出圖片請求或修改dom,這也是更貼近生產環境的真實場景
    html 代碼:

    <!DOCTYPE html>
    
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>test</title>
    
        <!-- 請求時長為1.2秒的css -->
        <link rel="stylesheet" href="http://localhost:3010/index.css?delay=1200">
    
        <!-- 請求時長為0.4秒的js -->
        <script src="http://localhost:3010/index.js?delay=400" async></script>
    </head>
    <body>
    </body>
    </html>
    
    

    index.js 代碼:

    async function getImage () {
        // 請求時長為0.5秒的介面請求
        await fetch('http://localhost:3010/api?delay=500')
    
        const img = document.createElement('img')
        // 請求時長為0.5秒的圖片資源
        img.src = 'http://localhost:3010/index.png?delay=500'
        document.body.appendChild(img)
    
    }
    
    getImage()
    
    

    結果圖如下:
    alt

    如圖所示,結合場景五記的結果,雖然普通的 api 請求並不會影響頁面載入時長,但是因為api請求過後,重新請求了圖片資源(或大量操作 dom),依然會導致頁面載入時間變長。這也是我們日常開發中最常見的場景,頁面載入了js,js發出網路請求,用於獲取頁面渲染數據,頁面渲染時載入圖片或進行dom操作。

  • 場景七:頁面多媒體資源的載入
    示例代碼:

    <!DOCTYPE html>
    
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>test</title>
    </head>
    <body>
        <video src="http://localhost:3010/video.mp4?delay=500" controls></video>
    </body>
    </html>
    
    

    結果如圖:
    alt

    對於視頻這種多媒體資源的載入比較有意思,video 標簽對於資源的載入是預設開啟 preload 的,所以資源會預設進行網路請求(如需關閉,要把 preload 設置為 none ),可以看到紅色豎線基本處於圖中綠色條和藍色條中間(實際上更偏右一些),圖片綠色部分代表資源等待時長,藍色部分代表資源真正的載入時長,且藍色載入條在onload的豎線右側,這說明多媒體的資源確實影響了 onload 時長,但是又沒完全影響,因為設置了500ms的延遲返回資源,所以 onload 也被延遲了500ms左右,但一旦視頻真正開始下載,這段時長已經不記錄在 onload 的時長中了。

    其實這種行為也算合理,畢竟多媒體資源通常很大,占用的帶寬也多,如果一直延遲 onload,意味著很多依賴 onload 的事件都無法及時觸發。

    接下來我們把這種情況再複雜一些,貼近實際的生產場景,通常video元素是包含封面圖 poster 屬性的,我們設置一張延遲1秒的封面圖,看看會發生什麼,結果如下:
    alt
    不出意外,果然封面圖影響了整體的載入時長,魔鬼都在細節中,封面圖也需要註意優化壓縮

  • 場景八:非同步腳本和樣式資源一同請求
    示例代碼:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>test</title>
    
        <!-- 請求時長為1秒的css -->
        <link rel="stylesheet" href="http://localhost:3010/index.css?delay=1000">
    
        <!-- 請求時長為0.5秒的js -->
        <script src="http://localhost:3010/index.js?delay=500" async></script>
    </head>
    <body>
    </body>
    </html>
    
    

    瀏覽器表現如下:
    alt
    可以看出 css 資源雖然沒有阻塞腳本的載入,但是卻延遲了整體頁面載入時長,其中原因是css資源的載入會影響 render tree 的生成,導致頁面遲遲不能完成渲染。
    如果嘗試把 async 換成 defer,或者乾脆使用同步的方式載入腳本,結果也是一樣,因結果相同,本處不再舉例。

  • 場景九:樣式資源先請求,再執行內聯腳本邏輯,最後載入非同步腳本
    我們把場景八的代碼做一個改造,在樣式標簽和非同步腳本標簽之間,加上一個只包含空格的內聯腳本,讓我們看看會發生什麼,代碼如下:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <script>
            console.log('頁面js 開始執行')
        </script>
    
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>test</title>
    
        <!-- 請求時長為1秒的css -->
        <link rel="stylesheet" href="http://localhost:3010/index.css?delay=2000">
    
        <!-- 此標簽僅有一個空格 -->
        <script> </script>
    
        <!-- 請求時長為0.5秒的js -->
        <script src="http://localhost:3010/index.js?delay=500" async></script>
    </head>
    <body>
    </body>
    </html>
    
    

    index.js 中的內容如下:

    console.log("腳本 js 開始執行");
    
    

    結果如下,這是一張 GIF,載入可能有點慢:
    alt
    這個結果非常有意思,他到底發生了什麼呢?

    1. 腳本請求是0.5秒的延遲,樣式請求是2秒

    2. 腳本資源是 async 的請求,非同步發出,應該什麼時候載入完什麼時候執行

    3. 但是圖中的結果卻是等待樣式資源載入完畢後才執行

    答案就在那個僅有一個空格的腳本標簽中,經反覆測試,如果把標簽換成註釋,也會出現一樣的現象,如果是一個完全空的標簽,或者根本沒有這個腳本標簽,那下方的index.js 通過 async 非同步載入,並不會違反直覺,載入完畢後直接執行了,所以這是為什麼呢?

    這其實是因為樣式資源下方的 script 雖然僅有一個空格,但是被瀏覽器認為了它內部可能是包含邏輯,一定概率會存在樣式的修改、更新 dom 結構等操作,因為樣式資源沒有載入完(被延遲了2秒),導致同步 js (只有一個空格的腳本)的執行被阻塞了,眾所周知頁面的渲染和運行是單線程的,既然前面已經有了一個未執行完成的 js,所以也導致了後面非同步載入的 js 需要在隊列中等待。這也就是為什麼 async 雖然非同步載入了,但是沒有在載入後立即執行的原因。

  • 場景十:字體資源的載入
    示例代碼:

    <!DOCTYPE html>
    
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>test</title>
        <style>
            @font-face {
                font-family: font-custom;
                src: url('http://localhost:3010/font.ttf?delay=500');
            }
    
            body {
                font-family: font-custom;
            }
        </style>
    </head>
    <body></body>
    </html>
    
    

    結果如下:
    alt
    可以看到,此情況下字體的載入是對 onload 有影響的,然後我們又測試了一下只聲明字體、不使用的情況,也就是刪除上面代碼中 body 設置的字體,發現這種情況下,字體是不會發出請求的,僅僅是造成了代碼的冗餘。

四、總結

前面列舉了大量的案例,接下來我們做個總結,實質性影響 onload 其實就是幾個方面。

  1. 圖片資源的影響毋庸置疑,無論是在頁面中直接載入,還是通過 js 懶載入,只要載入過程是在 onload 之前,都會導致頁面 onload 時長增加。

  2. 多媒體資源的等待時長會被記入 onload,但是實際載入過程不會。

  3. 字體資源的載入會影響 onload。

  4. 網路介面請求,不會影響 onload,但需要註意的是介面返回後,如果此時頁面還未 onload,又進行了圖片或者dom操作,是會導致 onload 延後的。

  5. 樣式不會影響腳本的載入和解析,只會阻塞腳本的執行。

  6. 非同步腳本請求不會影響頁面解析,但是腳本的執行同樣影響 onload。

五、優化舉措

  1. 圖片或其他資源的預載入可以通過 preload 或 prefetch 請求,這兩種方式都不會影響 onload 時長。

  2. 一定註意壓縮圖片,頁面中圖片的載入速度可能對整體時長有決定性影響。

  3. 儘量不要做串列請求,沒有依賴關係的情況下,推薦並行。

  4. 中文字體包非常大,可以使用字蛛壓縮、或用圖片代替。

  5. 靜態資源上 cdn 很重要,壓縮也很重要。

  6. 刪除你認為可有可無的代碼,沒準哪一行代碼就會影響載入速度,並且可能很難排查。

  7. 視頻資源如果在首屏以外,不要開啟預載入,合理使用視頻的 preload 屬性。

  8. async 和 defer 記得用,很好用。

  9. 非必要的內容,可以在 onload 之後執行,是時候重新拾起來這個 api 了。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 為什麼要分庫分表 用戶請求量太大 單伺服器TPS、記憶體、IO都是有上限的,需要將請求打散分佈到多個伺服器 。 單庫數據量太大 單個資料庫處理能力有限;單庫所在伺服器的磁碟空間有限;單庫上的操作IO有瓶頸 。 單表數據量太大 查詢、插入、更新操作都會變慢,在加欄位、加索引、機器遷移都會產生高負載,影響 ...
  • Mysql 01 使用CMD連接資料庫 -- 在控制台連接資料庫(需將位置切換到mysql所在地址) mysql -u root -p password:12345 -- 修改mysql賬戶密碼及許可權,安裝配置完後慎用 update mysql.user set authentication_str ...
  • 前言 不同微信版本,關閉廣告的方式不一樣,今天嘗試關閉微信版本 8.0.33 廣告,但是發現最後還是關不掉,故寫下此文,希望對大家死了關閉廣告這條心! 操作如下 步驟一:點擊設置—>關於微信 步驟二:點擊進入《隱私保護指引》 步驟三:在第一大類中找到 1.34 小類,其中有個藍色字樣《個性化廣告》, ...
  • Flutter 是由 Google 創建的免費開源的移動應用程式開發框架。可以用它為 iOS、Android 和 Web 平臺,開發界面華麗、高性能、響應式的應用程式。Flutter 基於 Dart 編程語言,並使用 Skia 圖形庫來渲染其組件。 和很多其它框架一樣,Flutter 宣稱其主要特性 ...
  • 最近用 TypeScript 寫 npm 包,各種模塊、命名空間、全局定義等等擾得我睡不著覺。我苦心研究,總結了幾個比較冷門的,國內貌似基本上找不到資料的導入導出用法,順便在其中又插入一些不那麼冷門的用法,於是本篇文章來了。 ...
  • 1.ref 和 $refs ref 被用來給元素或子組件註冊引用信息, 引用信息將會註冊在父組件的 $refs 對象上,如果是在普通的DOM元素上使用,引用指向的就是DOM元素,如果是在子組件上,引用就指向組件的實例。 $refs 是一個對象,持有已註冊過 ref 的所有的子組件。 ref用法: r ...
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 在DOM結構相對比較複雜,層級嵌套比較深的組件內,需要根據相對應的模塊業務處理一些邏輯,該邏輯屬於當前組件 但是從整個頁面應用的視圖上看,它在DOM中應該被渲染在整個vue應用外部的其他地方,不能影響組件的結構 比較常見的應用場景:就是全 ...
  • 這周每天花點時間學習使用VUE3+Django+GraphQL的使用,按照RealPython的網站的教程走了一遍,踩了一遍坑. Realpython上的教程使用的是Vue2的Vue-CLI模塊,Vue本身已經進化到VUE3,並且推薦使用Vite代替Vue-CLI.我按照教程上的步驟將代碼轉化為VU ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...