作者:京东科技 孙凯

一、前言

相信很多前端开发者在做项目时同时也都做过页面性能优化,这不单是前端的必备职业技能,也是考验一个前端基础是否扎实的考点,而性能指标也通常是每一个开发者的绩效之一。尤其马上接近年关,页面白屏时间是否过长、首屏加载速度是否达标、动画是否能流畅运行,诸如此类关于性能更具体的指标和感受,很可能也是决定着年底你能拿多少年终奖回家过年的晴雨表

关于性能优化,我们一般从以下四个方面考虑:

  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()
})

1. 脚本请求是 0.5 秒的延迟,样式请求是 2 秒

2. 脚本资源是 async 的请求,异步发出,应该什么时候加载完什么时候执行

3. 但是图中的结果却是等待样式资源加载完毕后才执行

答案就在那个仅有一个空格的脚本标签中,经反复测试,如果把标签换成注释,也会出现一样的现象,如果是一个完全空的标签,或者根本没有这个脚本标签,那下方的 index.js 通过 async 异步加载,并不会违反直觉,加载完毕后直接执行了,所以这是为什么呢?

这其实是因为样式资源下方的 script 虽然仅有一个空格,但是被浏览器认为了它内部可能是包含逻辑,一定概率会存在样式的修改、更新 dom 结构等操作,因为样式资源没有加载完(被延迟了 2 秒),导致同步 js(只有一个空格的脚本)的执行被阻塞了,众所周知页面的渲染和运行是单线程的,既然前面已经有了一个未执行完成的 js,所以也导致了后面异步加载的 js 需要在队列中等待。这也就是为什么 async 虽然异步加载了,但是没有在加载后立即执行的原因。

四、总结

前面列举了大量的案例,接下来我们做个总结,实质性影响 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 了。


↙↙↙阅读原文可查看相关链接,并与作者交流