前端里那些你不知道的事兒之 【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
  • 前言 本文介紹一款使用 C# 與 WPF 開發的音頻播放器,其界面簡潔大方,操作體驗流暢。該播放器支持多種音頻格式(如 MP4、WMA、OGG、FLAC 等),並具備標記、實時歌詞顯示等功能。 另外,還支持換膚及多語言(中英文)切換。核心音頻處理採用 FFmpeg 組件,獲得了廣泛認可,目前 Git ...
  • OAuth2.0授權驗證-gitee授權碼模式 本文主要介紹如何筆者自己是如何使用gitee提供的OAuth2.0協議完成授權驗證並登錄到自己的系統,完整模式如圖 1、創建應用 打開gitee個人中心->第三方應用->創建應用 創建應用後在我的應用界面,查看已創建應用的Client ID和Clien ...
  • 解決了這個問題:《winForm下,fastReport.net 從.net framework 升級到.net5遇到的錯誤“Operation is not supported on this platform.”》 本文內容轉載自:https://www.fcnsoft.com/Home/Sho ...
  • 國內文章 WPF 從裸 Win 32 的 WM_Pointer 消息獲取觸摸點繪製筆跡 https://www.cnblogs.com/lindexi/p/18390983 本文將告訴大家如何在 WPF 裡面,接收裸 Win 32 的 WM_Pointer 消息,從消息裡面獲取觸摸點信息,使用觸摸點 ...
  • 前言 給大家推薦一個專為新零售快消行業打造了一套高效的進銷存管理系統。 系統不僅具備強大的庫存管理功能,還集成了高性能的輕量級 POS 解決方案,確保頁面載入速度極快,提供良好的用戶體驗。 項目介紹 Dorisoy.POS 是一款基於 .NET 7 和 Angular 4 開發的新零售快消進銷存管理 ...
  • ABP CLI常用的代碼分享 一、確保環境配置正確 安裝.NET CLI: ABP CLI是基於.NET Core或.NET 5/6/7等更高版本構建的,因此首先需要在你的開發環境中安裝.NET CLI。這可以通過訪問Microsoft官網下載並安裝相應版本的.NET SDK來實現。 安裝ABP ...
  • 問題 問題是這樣的:第三方的webapi,需要先調用登陸介面獲取Cookie,訪問其它介面時攜帶Cookie信息。 但使用HttpClient類調用登陸介面,返回的Headers中沒有找到Cookie信息。 分析 首先,使用Postman測試該登陸介面,正常返回Cookie信息,說明是HttpCli ...
  • 國內文章 關於.NET在中國為什麼工資低的分析 https://www.cnblogs.com/thinkingmore/p/18406244 .NET在中國開發者的薪資偏低,主要因市場需求、技術棧選擇和企業文化等因素所致。歷史上,.NET曾因微軟的閉源策略發展受限,儘管後來推出了跨平臺的.NET ...
  • 在WPF開發應用中,動畫不僅可以引起用戶的註意與興趣,而且還使軟體更加便於使用。前面幾篇文章講解了畫筆(Brush),形狀(Shape),幾何圖形(Geometry),變換(Transform)等相關內容,今天繼續講解動畫相關內容和知識點,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 什麼是委托? 委托可以說是把一個方法代入另一個方法執行,相當於指向函數的指針;事件就相當於保存委托的數組; 1.實例化委托的方式: 方式1:通過new創建實例: public delegate void ShowDelegate(); 或者 public delegate string ShowDe ...