前端里那些你不知道的事儿之 【window.onload】
wxin55 2025-05-14 17:21 3 浏览 0 评论
作者:京东科技 孙凯
一、前言
相信很多前端开发者在做项目时同时也都做过页面性能优化,这不单是前端的必备职业技能,也是考验一个前端基础是否扎实的考点,而性能指标也通常是每一个开发者的绩效之一。尤其马上接近年关,页面白屏时间是否过长、首屏加载速度是否达标、动画是否能流畅运行,诸如此类关于性能更具体的指标和感受,很可能也是决定着年底你能拿多少年终奖回家过年的晴雨表。
关于性能优化,我们一般从以下四个方面考虑:
- 开发时性能优化
- 编译时性能优化
- 加载时性能优化
- 运行时性能优化
而本文将从第三个方面展开,讲一讲哪些因素将影响到页面加载总时长,谈到总时长,那总是避免不了要谈及 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>
浏览器表现如下:
通过上图可以看到,瀑布图中深蓝色竖线表示触发了 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>
浏览器表现如下:
由图可以得知使用了 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)
结果如下:
从瀑布图可以看出,资源的连续加载,导致了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)
结果示意:
表现是与场景三一样的,这个不再多说,但是有意思的来了,不一样的是,经过测试发现,哪怕删除最后一行代码:document.body.appendChild(img),不向页面中插入元素,图片也会发出请求,也同样延长了页面加载时长,所以部分同学就要注意了,这是一把双刃剑:当你真的需要懒加载图片时,可以少写最后一行插入元素的代码了,但是如果大量的图片加载请求发出,哪怕不向页面插入图片,也真的会拖慢页面的时长。
趁着这个场景,再多说一句,一些埋点数据的上报,也正是借着图片有不需要插入dom即可发送请求的特性,实现成功上传的。
场景五:普通接口请求
html 保持不变,index.js 内容如下:
// 请求时长为500毫秒的请求接口
fetch('http://localhost:3010/api?delay=500')
结果如下图:
可以发现普通接口请求的发出,并不会影响页面加载,但是我们再把场景弄复杂一些,见场景六。
场景六:同时加载样式、脚本,脚本加载完成后,内部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()
结果图如下:
如图所示,结合场景五记的结果,虽然普通的 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>
结果如图:
对于视频这种多媒体资源的加载比较有意思,video 标签对于资源的加载是默认开启 preload 的,所以资源会默认进行网络请求(如需关闭,要把 preload 设置为 none ),可以看到红色竖线基本处于图中绿色条和蓝色条中间(实际上更偏右一些),图片绿色部分代表资源等待时长,蓝色部分代表资源真正的加载时长,且蓝色加载条在onload的竖线右侧,这说明多媒体的资源确实影响了 onload 时长,但是又没完全影响,因为设置了500ms的延迟返回资源,所以 onload 也被延迟了500ms左右,但一旦视频真正开始下载,这段时长已经不记录在 onload 的时长中了。
其实这种行为也算合理,毕竟多媒体资源通常很大,占用的带宽也多,如果一直延迟 onload,意味着很多依赖 onload 的事件都无法及时触发。
接下来我们把这种情况再复杂一些,贴近实际的生产场景,通常video元素是包含封面图 poster 属性的,我们设置一张延迟1秒的封面图,看看会发生什么,结果如下:
不出意外,果然封面图影响了整体的加载时长,魔鬼都在细节中,封面图也需要注意优化压缩。
场景八:异步脚本和样式资源一同请求
示例代码:
<!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>
浏览器表现如下:
可以看出 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,加载可能有点慢:
这个结果非常有意思,他到底发生了什么呢?
- 脚本请求是0.5秒的延迟,样式请求是2秒
- 脚本资源是 async 的请求,异步发出,应该什么时候加载完什么时候执行
- 但是图中的结果却是等待样式资源加载完毕后才执行
答案就在那个仅有一个空格的脚本标签中,经反复测试,如果把标签换成注释,也会出现一样的现象,如果是一个完全空的标签,或者根本没有这个脚本标签,那下方的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>
结果如下:
可以看到,此情况下字体的加载是对 onload 有影响的,然后我们又测试了一下只声明字体、不使用的情况,也就是删除上面代码中 body 设置的字体,发现这种情况下,字体是不会发出请求的,仅仅是造成了代码的冗余。
四、总结
前面列举了大量的案例,接下来我们做个总结,实质性影响 onload 其实就是几个方面。
- 图片资源的影响毋庸置疑,无论是在页面中直接加载,还是通过 js 懒加载,只要加载过程是在 onload 之前,都会导致页面 onload 时长增加。
- 多媒体资源的等待时长会被记入 onload,但是实际加载过程不会。
- 字体资源的加载会影响 onload。
- 网络接口请求,不会影响 onload,但需要注意的是接口返回后,如果此时页面还未 onload,又进行了图片或者dom操作,是会导致 onload 延后的。
- 样式不会影响脚本的加载和解析,只会阻塞脚本的执行。
- 异步脚本请求不会影响页面解析,但是脚本的执行同样影响 onload。
五、优化举措
- 图片或其他资源的预加载可以通过 preload 或 prefetch 请求,这两种方式都不会影响 onload 时长。
- 一定注意压缩图片,页面中图片的加载速度可能对整体时长有决定性影响。
- 尽量不要做串行请求,没有依赖关系的情况下,推荐并行。
- 中文字体包非常大,可以使用 字蛛 压缩、或用图片代替。
- 静态资源上 cdn 很重要,压缩也很重要。
- 删除你认为可有可无的代码,没准哪一行代码就会影响加载速度,并且可能很难排查。
- 视频资源如果在首屏以外,不要开启预加载,合理使用视频的 preload 属性。
- async 和 defer 记得用,很好用。
- 非必要的内容,可以在 onload 之后执行,是时候重新拾起来这个 api 了。
相关推荐
- 总结雅虎前端性能优化技巧(16条)
-
前言在日常开发中,有很多场景需要我们去做好前端优化,为了防止遗忘,加深记忆,今天参阅了一些资料以及自己的一些总结,梳理出来15条优化技巧。1.合并文件css、js合并,减少http请求数,每次http...
- 前端掉坑血泪史!4 个 React 性能优化绝招让页面秒开
-
在前端圈子里摸爬滚打这么多年,我发现React开发时踩坑的经历大家都大同小异。页面加载慢、组件频繁重渲染、状态管理混乱……这些痛点,相信不少前端工程师都感同身受。别愁!今天就给大家分享4个超...
- Qwik:革新Web开发的新框架
-
听说关注我的人,都实现了财富自由!你还在等什么?赶紧加入我们,一起走向人生巅峰!Qwik:革新Web开发的新框架Qwik橫空出世:一场颠覆前端格局的革命?是炒作还是未来?前端框架的更新迭代速度,如同...
- 大模型服务平台百炼使用
-
提供完整的模型训练、微调、评估等产品工具,预置丰富的应用插件,提供便捷的集成方式,更快更高效地完成大模型应用的构建。一、通过变量的方式使用平台模板一个好的Prompt可以更好的让模型理解我们的需求,产...
- Vue应用性能优化实战:8 个提升页面加载速度的关键策略
-
一、构建优化与代码精简1.1代码分割与异步加载路由级代码分割:使用动态导入语法拆分路由组件组件级懒加载:结合Suspense实现按需加载javascript//vue-router4.x配置...
- 前端里那些你不知道的事儿之 【window.onload】
-
作者:京东科技孙凯一、前言相信很多前端开发者在做项目时同时也都做过页面性能优化,这不单是前端的必备职业技能,也是考验一个前端基础是否扎实的考点,而性能指标也通常是每一个开发者的绩效之一。尤其马上接近...
- 谷歌站长后台的“核心网页指标”不合格先优化哪个最有效?
-
根据对上千个网站案例的分析,90%的站长在修复时都陷入“盲目优化”误区——要么死磕服务器配置却忽略图片规范,要么过度压缩JS反而引发CLS布局错位。事实上,移动端页面抖动(CLS)才是60%中小网站的...
- Vue3 开发效率拉胯?这 10 个技巧让你开发速度翻倍!
-
写Vue3项目时,是不是经常被数据更新延迟、组件间传值混乱、页面卡顿这些问题搞得焦头烂额?别担心!今天带来10个超实用的Vue3实战技巧,全是从真实项目中总结出来的“血与泪”经验,帮你...
- 2024年的JavaScript性能优化:仍然重要吗?
-
#记录我的9月生活#在不断发展的Web开发领域,新的JavaScript框架和库令人眼花缭乱,很容易让人忽视一些基本的东西。但在这股兴奋之中,性能作为一个卓越用户体验的基石,不能被忽略。为什么?因为...
- JS 图片简易压缩【实践】
-
作者:政采云前端团队转发链接:https://juejin.im/post/5ea574cc518825736e57fcca前言说起图片压缩,大家想到的或者平时用到的很多工具都可以实现,例如,客户端类...
- Vue3 开发总踩坑?这 10 个技巧让你少走半年弯路!
-
前端开发的路上,Vue3虽然强大,但坑也不少!性能优化总没效果?复杂组件通信一头雾水?别担心!今天分享10个超实用的Vue3实战技巧,全是一线开发总结的经验,帮你轻松避开开发雷区,效率直接拉...
- 前端分享-Vue首屏加载优化
-
首屏加载速度直接影响用户留存率——当加载时间超过3秒,53%的用户会直接离开(网上来的数据)。Vue单页应用尤需重视,因为传统打包方案会将所有资源打包成巨大的vendor.js,导致用户首次访问时像下...
- Core Web Vitals 变了,网站性能这件事得重新关注
-
现在做网站优化,不能只看速度条,不管你是搞外贸独立站,还是给品牌建站,体验页面这件事你迟早得面对。谷歌这两年把网站的“体验感”提得越来越多,尤其是CoreWebVitals(网页核心指标)一出来,...
- 页面卡顿到崩溃?5 个实战技巧让前端性能飙升 80%!
-
作为前端工程师,你有没有遇到过这种情况:精心开发的页面,一上线就被用户吐槽卡顿、加载缓慢,甚至频繁崩溃。明明代码逻辑没问题,可性能就是上不去,这到底是哪里出了问题?别着急,今天就来分享5个超级实用...
- 周末复习前端js基础知识点总结一,记录完之后好复习(大佬勿喷)
-
一、深浅拷贝知识1、基本数据类型只有赋值没有拷贝2、数组和对象的赋值是浅拷贝3、结构赋值是深拷贝还是浅拷贝?二、实现深拷贝的几种常用方法方法1、通过json方法深拷贝方法2.基本的封装深拷贝的方法采用...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- hive行转列函数 (63)
- sourcemap文件是什么 (54)
- display none 隐藏后怎么显示 (56)
- 共享锁和排他锁的区别 (51)
- httpservletrequest 获取参数 (64)
- jstl包 (64)
- qsharedmemory (50)
- watch computed (53)
- java中switch (68)
- date.now (55)
- git-bash (56)
- 盒子垂直居中 (68)
- npm是什么命令 (62)
- python中+=代表什么 (70)
- fsimage (51)
- nginx break (61)
- mysql分区表的优缺点 (53)
- centos7切换到图形界面 (55)
- 前端深拷贝 (62)
- kmp模式匹配算法 (57)
- jsjson字符串转json对象 (53)
- jdbc connection (61)
- javascript字符串转换为数字 (54)
- mybatis 使用 (73)
- 安装mysql数据库 (55)