网站性能优化实践

发布于 2021-12-12 22:15:32 字数 8143 浏览 1099 评论 0

加载过慢导致的白屏、计算耗时太久导致响应不及时或卡顿,都会降低用户对网站的评价。为了提高网站的性能,我们需要分析网站从加载到最终呈现、以及用户操作的响应的过程中,究竟发生了什么。

1. 使用工具衡量性能

在开始进行优化之前,需要强调一点是:必须以真实统计的数据为依据,优化过程中必须看妨碍性能的关键指标是否发生了变化。

PageSpeed Insights 是由 Google 提供的一个检测网站性能的工具,输入网站地址后它会返回网站性能的得分,以及具体改善性能的建议,可以看到它提供了在 移动设备 和 桌面设备的不同评分,可以针对不同设备来做具体的优化。

pagespeed-insights-leetcode.png

上图可以看到性能的评分为40分,然后有 首次内容渲染、首次输入延迟 以及 实验室数据等多项统计数据,其中绿/橙/红分别代表了优/一般/差的表现。同时它还提供了优化建议和诊断结果,有助于用户改善网站性能。在随后的部分,我们会逐步解释各个指标、优化建议分别代表什么含义。

Chrome DevTools 作为最重要的网页开发调试工具,几乎能查看到网页加载使用过程的一切信息,作为开发者有必要熟练运用开发者工具。

chrome-devtools-leetcode.png

上图可以看到很多信息,包含了FPS/CPU/Networks/Frames/Interactions/Timing,我们先选中Main这一项,可以看到统计了5.67s内的监测数据,底部面板呈现了在Loading、Script、Renddering等阶段的耗时。将底部的Tab切换到Event Log,并且将Scripting和Rendering取消勾选,可以看到各个时间点发生的事件,下图截取了一部分:

chrome-devtools-leetcode-summary-event-log.png
chrome-devtools-leetcode-summary-event-log-more.png

最头部的 Send Request 和 Receive Response 成功请求获取到了HTML的内容,然后在parseHTML中,解析出了对更多资源的加载,于是又触发了更多新的请求,Finish Loading 事件则标志着某个资源的加载的结束。

这里只是一个开头,后续就要开始介绍影响性能的两个途径,即:优化关键渲染路径、优化代码书写方式。借助于上述的工具,我们能够很好的衡量网站在具体指标上的表现。

2. 优化关键渲染路径

2.1 关键渲染路径

关键渲染路径 简单来说就是浏览器将HTML、CSS和JavaScript转化为可见的像素的过程。这个过程包含如下几个步骤:

关键渲染路径

网页的第一请求个是 HTML 请求,请求到达服务器后,服务器处理并且返回响应,客户端收到响应后开始进行处理,最后进行展示和布局,对应的过程为:

  • request
  • loading
  • response
  • scripting:parse,DOM Tree,CSSOM Tree,Render Tree
  • rendering:layout
  • painting: paint

在 scripting 阶段,HTML parsing 过程创建了 DOM(document object model)Tree,遇到任意链接的外部资源(比如样式文件、js文件、图片文件),浏览器都会发起一个新的请求。

浏览器会持续不断进行 parsing 的过程构建 DOM,发送获取资源的请求,直到最终结束以后,它开始构建 CSSOM(CSS object model)。在 DOM 和 CSSOM 都完成了,浏览器会构建好 Render Tree,计算所有可见内容的样式。

在 rendering 阶段,Render Tree 构建好以后,开始进行 Layout 操作,它决定元素放置的位置和大小。

等这一切结束以后,在 painting 阶段,网页通过称为被 paint 的步骤被绘制出来。

2.2 资源的阻塞问题

关于关键渲染路径过程,非常重要的一点是,一些资源的请求是阻塞性的。这里先列举一般性的结论:

  • HTML资源最先加载,其他资源依次并行加载,完成时间和加载顺序无关,和网络、文件大小有关。
  • JS的加载,会阻塞HTML parse 和 CSS render;JS的执行按照标签出现的顺序执行。
  • CSS的加载,会阻塞HTML parse 和 JS 执行;CSS render需要整个文件加载解析完成,CSS render的顺序和标签的出现顺序相同。

资源并行加载

文件加载通常先出现的标签先加载,但并不串行,即它们的过程可同时进行。结果就是一些比较大的文件虽然先加载,但可能后加载完成。

// html-CRP-block.html
<head>
  <script src="https://code.jquery.com/jquery-3.4.1.js"></script>
  <script src="link-jquery.js"></script>
  <link rel="stylesheet" href="bg-color.css"></link>
</head>
<body>
  <p>Hello, CRP!!!</p>
</body>
//link-jquery.js
console.log('jquery download and executed ? ' + (!!$ ? 'yes' : 'not ye'));
//bg-color.css
p {
    background: red;
}

我们用 Chrome Devtools 的 Performance 记录下来网络情况(如下图),首当其冲的是 HTML 内容,然后是本地的 link-jquery.jsbg-color.css 文件,虽然 jquery-3.4.1.js 先出现但是却后开始加载,也是后开始完成。

resource load

JS 的按照加载顺序执行

如下为 console 中的输出结果,可见虽然js后面的先加载完,但是执行顺序还是严格依赖出现顺序的。

jquery download and executed ? yes

JS 的加载解析阻塞 HTML/CSS 的解析

// html-CRP-block.html
<head>
  <link rel="stylesheet" href="bg-color.css"></link>
</head>
<body>
  <script src="https://code.jquery.com/jquery-3.4.1.js"></script>
  <script src="link-jquery.js"></script>
  <p id="hello">Hello, CRP!!!</p>
</body>
//link-jquery.js
console.log(document.getElementById('hello'));

如上代码中,我们可以在 console 中看到的输出结果为 null,说明 JS的加载执行过程,block 了 HTML 的解析,因为直接查询 DOM Tree 是没有该结构的。

//bg-color-red.css
p {
    background: red;
}
//bg-color-green.css
p {
    background: green;
}
// html-CRP-block.html
<body>
  <p id="hello">Hello, CRP!!!</p>
  <link rel="stylesheet" href="bg-color-red.css"></link>
  <script src="https://code.jquery.com/jquery-3.4.1.js"></script>
  <link rel="stylesheet" href="bg-color-green.css"></link>
</body>

稍微修改代码,将 Devtools 的网络调整到 Slow 3G,刷新界面可以看到背景色首先是红色,然后过了数秒后变成绿色。

同理,我们可以通过 Devtools 的 Performance 看到,最先加载的蓝色区域是HTML内容,两个紫色的CSS文件都早于jquery加载完成,但是一直等到jquery加载和执行完成以后,才会继续parseHTML、Parse StyleSheet,并且最终触发DOMContentReady事件,因此 JS的加载同样也阻塞 CSS 的解析和渲染。

CSS 的加载

CSS文件的加载是加载和解析同步进行的,但只有等到整个CSS文件都被解析好,才会绘制CSSOM并且完成绘制,这是因为CSS文件中属性是可以相互覆盖的,加载部分就进行渲染无疑会降低性能。

同样如下例子可以看到,界面长时间处于白色背景,然后才出现红色背景,和无padding的body,可见 CSS 的绘制,同样按照标签的出现顺序进行绘制

// html-CRP-block.html
<body>
  <p id="hello">Hello, CRP!!!</p>
  <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" rel="stylesheet">
  <link rel="stylesheet" href="bg-color-red.css"></link>
</body>

上述例子在网页中的效果是,先看到文字

CSS 的加载对 HTML/JS 的影响

//bg-color-red.css
p {
    background: red;
}
// html-CRP-block.html
<body>
  <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" rel="stylesheet">
  <p id="hello">Hello, CRP!!!</p>
  <script src="link-jquery.js"></script>
  <link rel="stylesheet" href="bg-color-red.css"></link>
</body>

网页中打开上述HTML可以看到,首先是页面空白,并且console中没有输出,然后过了数秒,才会出现红色背景的文字区域。

打开分析工具录制,可以看到蓝色的HTML先加载、然后是link-jquery.js、background-red.css先完成(虽然bootstrap.css先加载),在bootstrap.css加载完后,先Parse Stylesheet,然后继续 Parse HTML、Evaluate Script、Recalculate Style,可见 CSS的加载和解析也会阻塞HTML的解析 和 JS的解析执行

css-block-parse

css-block-parse

2.3 如何优化关键渲染路径

在关键渲染路径的什么阶段做优化,并没有绝对的规则,一切都要以工具衡量的结果为出发点。

我可以把阻塞的类型简单分为:阻塞加载、阻塞执行、阻碍渲染。

浏览器的parsing过程的,

2.4 项目中进行的实践

// package.json

"scripts": {
    "generate": "webpack --config webpack.generate.js --profile --json > stats.json",
    "analyze": "webpack-bundle-analyzer --port 8888 stats.json"
}

"devDependencies": {
    "webpack-bundle-analyzer": "^3.6.0",
}

devSever 环境下,请求的资源大小 Top 5

总计 16.16M

  • echarts 2.46M
  • elemenet-ui 1.82M
  • xlsx.js 1.35M
  • zrender/lib 488KB, refered by echarts
  • lodash.js 466KB
  • mediaelement 321KB
  • jquery.js 298KB

(1)echarts+zrender/lib,仅在项目中绘制了一个 chart。

(2)loadash只引用到了4个方法 upperFirst、camelCase、debounce、sizes。

(3)jquery只用到了extend方法。

可以通过覆写上述方法后,或者寻找替代方案后,将对应工具库从项目中移除。

尝试将(1)(3)移除后,再执行 analyze

总计 13.91M,减少的大小正好等于 echarts、zrender/lib、jquery.js 的大小。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

0 文章
0 评论
366 人气
更多

推荐作者

醉城メ夜风

文章 0 评论 0

远昼

文章 0 评论 0

平生欢

文章 0 评论 0

微凉

文章 0 评论 0

Honwey

文章 0 评论 0

qq_ikhFfg

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文