Web 网页性能及性能优化

一、Web 性能

Web 性能是 Web 开发的一个重要方面,侧重于网页加载速度以及对用户输入的响应速度

通过优化网站来改善性能,可以在为用户提供更好的体验

网页性能既广泛又非常深入

1. 为什么性能这么重要?

1. 性能关乎留住用户

性能对于任何在线业务都至关重要

与加载速度缓慢、让人感觉运行缓慢的网站相比,加载速度快并能及时响应用户输入的网站能更好地吸引并留住用户

2. 性能能提高转化次数

性能会对网站用户是否会浏览应用产生重大影响

3. 性能关乎用户体验

随着网页开始加载,用户会等待一段时间,等待内容显示。在此之前,就谈不上用户体验

快速连接会让这种体验一闪而过。而如果连接速度较慢,用户就不得不等待

Web 网页性能优化插图

性能是打造良好用户体验的基本要素

当网站发送大量代码时,浏览器必须使用用户流量套餐中的兆字节流量下载应用

尤其是移动设备的 CPU 性能和内存有限。这可能会导致糟糕的性能条件,而且考虑到人们了解人类的行为,用户只能容忍网站上的不利条件长达很长的时间,然后才会放弃网站

2. 网页核心指标

2.1. 指标类型:
  • 感知加载速度:网页可以多快地加载网页中的所有视觉元素并将其渲染到屏幕上

  • 加载响应速度:页面加载和执行组件快速响应用户互动所需的任何 JavaScript 代码的速度

  • 运行时响应速度:网页在加载后对用户互动的响应速度

  • 视觉稳定性:页面上的元素是否会以用户意想不到的方式发生偏移,是否可能会干扰用户的互动?

  • 流畅性:过渡和动画是否以一致的帧速率渲染,并在一种状态之间流畅地流动?

2.2. 要衡量的指标:
  • FCP(First Contentful Paint):从网页开始加载到网页内容的任何部分呈现在屏幕上所用的时间

  • LCP(Largest Contentful Paint):从网页开始加载到屏幕上呈现最大的文本块或图片元素所用的时间

  • INP(Interaction to Next Paint):与网页进行的每次 tapclick 或键盘互动的延迟时间,并根据交互的数量选择页面中最差的交互延迟作为单个代表性值来描述页面的总体响应性

  • TBT(Total Blocking Time):从 FCP 到可交互时间 (TTI) 之间的总时长

  • CLS(Cumulative Layout Shift):从页面开始加载到其生命周期状态更改为隐藏期间发生的所有意外布局偏移的累计分数

  • TTFB(Time to First Byte):网络使用资源的第一个字节响应用户请求所花费的时间

  • FID(First Input Delay):用户首次与网页互动(即,点击链接、点按按钮或使用由 JavaScript 提供支持的自定义控件)到浏览器实际能够开始处理事件处理脚本以响应相应互动的时间

2.3. Web 页面性能衡量指标-以用户为中心的性能指标

Web 页面性能衡量指标-以用户为中心的性能指标

二、性能优化

1. HTML 页面性能优化

每个网站都是从请求 HTML 文档开始的,该请求对网站的加载速度有着重大影响

要想构建可快速加载的网站,第一步就是要及时从服务器接收网页 HTML 的响应

当在浏览器的地址栏中输入网址时,浏览器会向服务器发送 GET 请求进行检索

网页的第一个请求针对的是 HTML 资源,因此,确保 HTML 以最短延迟快速到达是关键性能目标

1.1. 尽量减少重定向

在请求资源时,服务器可能会做出一个重定向响应,该重定向可以是永久重定向(301 Moved Permanently 响应)或临时重定向(302 Found 响应)

重定向会降低网页加载速度,因为它需要浏览器在新位置发出额外的 HTTP 请求来检索资源。重定向有两种类型:

  1. 完全发生在源站内的同源重定向。这些类型的重定向完全由项目控制,因为管理它们的逻辑完全位于的 Web 服务器上
  2. 由其他源启动的跨域重定向。这些类型的重定向通常无法控制
1.2. 缓存 HTML 响应

缓存 HTML 响应很困难,因为响应可能包含指向其他关键资源(例如 CSSJavaScript、图片和其他资源类型)的链接。这些资源的文件名中可能包含唯一指纹,该指纹会根据文件的内容而变化

但是较短的缓存生命周期(而不是不缓存)具有诸多优势:

  • 允许在 CDN 中缓存资源,减少从源服务器传送的请求数量

  • 在浏览器中传送资源,从而重新验证资源而不是再次下载此

  • 可以将缓存资源的适当时间设置为合适的分钟数

缓存 HTML 的一种方法是使用 ETagLast-Modified 响应标头

ETag(也称为实体标记)标头是一个标识符,用于唯一标识所请求资源,通常使用资源内容的哈希值:

ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"

每当资源发生变化时,都必须生成新的 ETag 值。在后续请求中,浏览器会通过 If-None-Match 请求标头发送 ETag 值。如果服务器上的 ETag 与浏览器发送的 ETag 匹配,服务器会返回 304 Not Modified 响应,浏览器则会使用缓存中的资源。虽然这仍然会导致网络延迟,但 304 Not Modified 响应比整个 HTML 资源小得多

但是,重新验证资源的新鲜度涉及的网络延迟也本身也是一个缺点,需自行决定以这种方式缓存 HTML 的额外工作是否值得,或者最好是谨慎操作,不必费心缓存 HTML 内容。

1.3. 测量服务器响应时间

如果响应未缓存,则服务器的响应时间在很大程度上取决于的托管服务提供商和后端应用堆栈

与动态网页相比,提供动态生成的响应(例如从数据库获取数据)的网页的 TTFB 可能更高,无需在后端投入大量计算时间即可立即提供

1.4. 压缩

基于文本的响应(例如 HTMLJavaScriptCSSSVG 图片)应进行压缩,以减小通过网络传输时的大小,从而加快其下载速度。最常用的压缩算法是 gzipBrotliBrotligzip 提高了约 15% 到 20%。

  • 尽可能使用 Brotli,所有主流浏览器都支持 Brotli,但如果网站有大量用户在旧版浏览器中使用,请确保将 gzip 用作后备选项,因为任何压缩都比不进行压缩要好。
  • 文件大小至关重要。非常小的资源(小于 1 KiB)压缩得不太好,有时甚至根本压缩不到。任何类型的数据压缩的效果都取决于能够使用压缩算法找到更多可压缩数据位的大量数据。文件越大,压缩效果就越好
  • 了解动态压缩和静态压缩。动态压缩和静态压缩是确定何时应压缩资源的不同方法
    • 动态压缩会在请求资源时压缩资源,有时甚至在每次请求资源时压缩资源。
    • 静态压缩消除了压缩本身涉及的延迟时间,在使用动态压缩的情况下,这可能会增加服务器响应时间。JavaScriptCSSSVG 图片等静态资源应静态压缩,而 HTML 资源应动态压缩。
1.5. CDN

CDN 是分布式服务器网络,服务器从源服务器缓存资源,反过来再从物理上更靠近用户的边缘服务器传送资源。在距离用户较近时,可以缩短往返时间 (RTT),而 HTTP/2HTTP/3、缓存和压缩等优化技术则可以让 CDN 更快地提供内容,而不是从源服务器提取内容。在某些情况下,使用 CDN 可以显著改善网站的 TTFB

2. 关键渲染路径

关键渲染路径是网页性能中的一个概念。

关键渲染路径是指网页开始在浏览器中呈现之前所涉及的步骤。为了呈现网页,浏览器需要 HTML 文档本身以及呈现该文档所需的所有关键资源。

2.1. 渐进式渲染

网络是自然分布的。与客户端和 APP 不同,浏览器不能依赖于拥有呈现页面所需的所有资源的网站。因此,浏览器非常擅长渐进式呈现页面。原生应用通常有一个安装阶段,然后是运行阶段。然而,对于网页和网络应用来说,这两个阶段之间的界限就不那么明显了。

2.2. 关键渲染路径

浏览器需要知道它应该等待的最小资源数量,以避免呈现明显不正常的体验。

另一方面,浏览器也不应该等待超过必要的时间才向用户显示一些内容。浏览器在执行初始呈现之前所采取的步骤序列称为关键渲染路径。

呈现路径涉及以下步骤:

  • 通过 HTML 构建文档对象模型 (DOM)

  • 通过 CSS 构建 CSS 对象模型 (CSSOM)

  • 应用任何会更改 DOMCSSOMJavaScript

  • 通过 DOMCSSOM 构建渲染树

  • 在页面上执行样式和布局操作,看看哪些元素适合显示

  • 在内存中绘制元素的像素

  • 如果有任何像素重叠,则合成像素

  • 以物理方式将所有生成的像素绘制到屏幕上

Web 网页性能优化插图(1)

只有在完成所有这些步骤后,用户才会在屏幕上看到内容

这一呈现过程会发生多次。初始渲染会调用此流程,但随着更多会影响网页渲染的资源可用,浏览器将会重新运行此流程(或许只是其中的一部分),以更新用户看到的内容。关键渲染路径侧重于之前为初始渲染概述的流程,并依赖于执行初始渲染所需的关键资源

2.3. 关键渲染路径上有哪些资源?

浏览器需要等待一些关键资源下载完毕,然后才能完成初始渲染。这些资源包括:

  • HTML 的一部分
  • 元素中阻塞渲染的 CSS
  • 元素中的阻塞渲染的 JavaScript

关键在于浏览器以流式方式处理 HTML。浏览器一旦获取网页 HTML 的任何部分,就会开始对其进行处理。然后,浏览器就可以(并且通常确实)决定先呈现网页,然后再接收网页的其余部分 HTML

3. 优化资源加载

网页加载时,其 HTML 中会引用许多资源,通过 CSS 提供网页的外观和布局,并通过 JavaScript 提供互动性。

3.1. 渲染阻塞

CSS 是一种阻塞渲染的资源,因为它会阻止浏览器渲染任何内容,直至构建了 CSS 对象模型 (CSSOM)。浏览器会阻止呈现,以防止出现非样式内容闪烁 (FOUC)

渲染阻塞未必是不可取的,但需要通过对 CSS 进行优化来最大限度地缩短其持续时间

3.2. 预加载扫描器
3.2.1. 什么是预加载扫描程序?

预加载扫描程序的角色是推测性,也就是说,它会检查原始标记,以便查找资源,以便在主要 HTML 解析器发现之前抓取相应资源

预加载扫描程序是一种浏览器优化,采用辅助 HTML 解析器的形式,可扫描原始 HTML 响应,以找出并推测性地提取资源,然后主 HTML 解析器才会发现这些资源

为了充分利用预加载扫描器,服务器发送的 HTML 标记中应包含关键资源。预加载扫描器无法发现以下资源加载模式:

  • CSS 使用 background-image 属性加载的图片。这些图片引用位于 CSS 中,预加载扫描器无法发现这些引用
  • 动态加载的脚本,采用 元素标记(使用 JavaScript 注入 DOM)或使用动态 import() 加载的模块
  • 使用 JavaScript 在客户端上呈现的 HTML
  • CSS @import 声明

这些资源加载模式都是后来发现的资源,请尽可能避免,如果无法避免此类模式,可以使用 preload 提示来避免资源发现延迟

3.3. CSS

CSS 决定了网页的呈现方式和布局,CSS 是一种阻止呈现的资源,因此优化 CSS 可能会对整体网页加载时间产生重大影响

3.3.1. 缩减大小

缩减 CSS 文件大小可缩减 CSS 资源的文件大小,从而缩短下载速度。这主要是通过从 CSS 源文件中移除内容(例如空格和其他不可见字符)并将结果输出到新优化的文件来实现的:

/* Heading 1 */
h1 {
  font-size: 2em;
  color: #000000;
}

/* Heading 2 */
h2 {
  font-size: 1.5em;
  color: #000000;
}
/* Minified CSS: */
h1,h2{color:#000}h1{font-size:2em}h2{font-size:1.5em}

就最基本的形式而言,CSS 缩减是一种有效的优化,可以提高网站的 FCP,在某些情况下或许甚至是 LCP

3.3.2. 移除未使用的 CSS

在呈现任何内容之前,浏览器需要先下载并解析所有样式表。完成解析所需的时间还包括当前网页上未使用的样式。如果使用的打包器将所有 CSS 资源合并到一个文件中,那么的用户下载的 CSS 可能会比呈现当前网页所需的数量多

如需发现当前网页未使用的 CSS,可以使用 Chrome 开发者工具中的 coverage

Web 网页性能优化插图(2)

移除未使用的 CSS 会产生双重效果:除了缩短下载时间之外,还可以优化渲染树的构建,因为浏览器需要处理的 CSS 规则更少

3.3.3. 避免使用 CSS @import 声明

CSS 中的 @import 声明允许从样式表中导入外部 CSS 资源

可以使用 元素替换 @import

3.3.4. 内嵌关键 CSS

关键 CSS 是指渲染在初始窗口中可见的内容所需的样式。初始窗口的概念有时称为“首屏”。网页上的其余内容将保持未设置样式,而其余的 CSS 将异步加载。

但其缺点是,内嵌大量 CSS 会导致初始 HTML 响应的字节增多。由于 HTML 资源通常无法缓存很长时间(甚至根本无法缓存),因此对于可能在外部样式表中使用同一 CSS 的后续网页,系统不会缓存内联的 CSS

需测试和衡量网页的性能

3.4. JS

加载过多的 JavaScript 可能会导致网页在网页加载期间响应缓慢,甚至可能导致响应速度问题减慢互动速度

3.4.1. 阻止呈现的 JS

加载不带 deferasync 属性的 元素时,浏览器会阻止解析和呈现,直到脚本下载、解析并执行完毕。同样,内联脚本也会阻止解析器,直到解析和执行脚本。

3.4.2. async 与 defer

asyncdefer 允许加载外部脚本,而不会阻止 HTML 解析器,而具有 type="module" 的脚本(包括内嵌脚本)会自动延迟。不过,asyncdefer 之间存在一些差异

Web 网页性能优化插图(3)

  • 使用 async 加载的脚本会在下载后立即解析和执行
  • 使用 defer 加载的脚本会在 HTML 文档解析完成时执行,这与浏览器的 DOMContentLoaded 事件同时发生
  • async 脚本可能会不按顺序执行
  • defer 脚本则会按照它们在标记中出现的顺序执行

使用 type="module" 属性加载的脚本会处于延迟状态,而使用 JavaScript 标记注入 DOM 中加载的脚本则像 async 脚本

3.4.3. 客户端渲染

应避免使用 JavaScript 来呈现任何关键内容或网页的 LCP 元素。这称为客户端渲染,是一种在单页应用 (SPA) 中广泛使用的技术

3.4.3.1. LCP 元素
  • Web 网页性能优化插图(4)

    5. 图片加载性能

    图片代表了当今许多网页上传输的大部分数据

    图片通常是网络上最庞大且最普遍的资源,在大多数情况下,优化图片意味着通过减少发送的字节数来减少网络时间,但也可以通过传送适合用户设备大小的图片,从而优化发送给用户的字节数

    可以使用 Web 网页性能优化插图(5)

    代码拆分是一项可以减少页面初始 JavaScript 载荷的实用技术。它可让将 JavaScript 软件包拆分为两部分:

    • 网页加载时所需的 JavaScript 无法在任何其他时间加载
    • 可在稍后时间点加载(最常见的是用户与页面上的指定互动元素互动时)的其余 JavaScript
    8.2. import()

    可以使用动态 import() 语法完成代码拆分。此语法与在启动期间请求指定 JavaScript 资源的 元素不同,该语法可在网页生命周期的后期请求 JavaScript 资源

    动态 import() 是一种类似于函数的表达式,可让动态加载 JavaScript 模块。 它是一种异步操作,可用于导入模块以响应互动或需要加载其他模块的其他任何条件。动态 import() 与静态import 语句不同,后者会立即导入模块,并且要求父模块及其所有依赖项都得到解析和执行,然后才能运行

    document.querySelectorAll('#myForm input').addEventListener('blur', async () => {
      const { validateForm } = await import('/validate-form.mjs');
      validateForm();
    }, { once: true });
    

    9. 延迟加载 元素

    元素可能会占用大量带宽和 CPU 处理时间

    与其他类型的资源相比, 元素消耗的带宽通常更多。对于 元素,加载和渲染其中的页面可能会消耗相当多的额外处理时间

    9.1. 元素的 loading 属性

    所有主流浏览器也都支持 元素上的 loading 属性

    loading 属性的值及其行为与使用 loading 属性的 Web 网页性能优化插图(6)

    如需使用 Service Worker 预缓存资源,可以使用 Workbox

    Workbox 使用预缓存清单来确定应预缓存的资源,预缓存清单是一个文件和版本控制信息列表,可作为要预缓存的资源的可信来源

    [{
        url: 'script.ffaa4455.js',
        revision: null
    }, {
        url: '/index.html',
        revision: '518747aa'
    }]
    

    上述代码是一个示例清单,其中包含 script.ffaa4455.js/index.html 这两个文件。如果资源在文件本身中包含版本信息(称为文件哈希),则 revision 属性可以保留为 null,因为文件已进行版本控制(例如,上述代码中 script.ffaa4455.js 资源的 ffaa4455 属性)。
    设置后,Service Worker 可用于预缓存静态页面或其子资源,以加快后续页面导航的速度

    workbox.precaching.precacheAndRoute([
      '/styles/product-page.ac29.css',
      '/styles/product-page.39a1.js',
    ]);
    

    Service Worker 使用的 Cache 接口和 HTTP 缓存并不相同

    Cache 接口是由 JavaScript 控制的高层级缓存,而 HTTP 缓存是由 Cache-Control 标头控制的低层级缓存

    与使用资源提示或推测规则预提取或预呈现资源类似,Service Worker 预缓存会消耗网络带宽、存储空间和 CPU

    建议仅预缓存可能会使用的资源,并在预缓存清单中指定过多的资源

    11. Web Worker

    用户在浏览器中看到的大部分内容都在称为主线程的单个线程上完成。不过,在某些情况下,可以启动新线程来执行计算开销很大的工作,以便主线程可以处理面向用户的重要任务。执行此操作的 API 称为 Web Worker API

    JavaScript 通常被描述为一种单线程语言。这是指主线程,这是浏览器执行在浏览器中看到的大部分工作的单个线程。其中包括编写脚本、某些类型的渲染工作、HTMLCSS 解析以及其他类型的面向用户的工作来改善用户体验等

    JavaScript 而言,通常只能在主线程上执行工作,但可以在 JavaScript 中注册和使用其他线程。允许在 JavaScript 中实现多线程的功能称为 Web Workers API

    11.1. Web Worker 启动方式

    实例化 Worker

    const myWebWorker = new Worker('/my-web-worker.js');
    
    11.2. Web Worker 的限制

    与在主线程上运行的 JavaScript 不同,Web Worker 无法直接访问 window上下文,并且对其提供的 API 的访问受到限制。Web Worker 受到以下限制条件的约束:

    • Web Worker 无法直接访问 DOM
    • Web Worker 可以通过消息传递流水线与 window 上下文进行通信,这意味着 Web Worker 可以通过某种方式间接访问 DOM
    • Web Worker 的作用域是 self,而不是 window
    • Web Worker 范围_确实_可以访问 JavaScript 基元和构造,以及 fetchAPI 和相当多的其他 API
    11.3. Web Worker 如何与 window 通信

    Web Worker 可以通过消息传递流水线与主线程的 window 上下文进行通信。利用此流水线,可以将数据传送到主线程和 Web 工作器以及从主线程和 Web 工作器传输数据。如需将数据从 Web Worker 发送到主线程,需要在 Web Worker 的上下文 (self) 中设置 message 事件

    // my-web-worker.js
    self.addEventListener("message", () => {
      // Sends a message of "Hellow, window!" from the web worker:
      self.postMessage("Hello, window!");
    });
    

    然后,在主线程上 window 上下文的脚本中,可以使用另一个 message 事件接收来自网页工作器线程的消息:

    // scripts.js
    // Creates the web worker:
    const myWebWorker = new Worker('/js/my-web-worker.js');
    // Adds an event listener on the web worker instance that listens for messages:
    myWebWorker.addEventListener("message", ({ data }) => {
      // Echoes "Hello, window!" to the console from the worker.
      console.log(data);
    });
    

    三、总结

    • 本文概述了性能以及性能的重要性
    • 罗列了性能优化的点
    • 希望对大家有帮助

    引用

    • performance
    • web performance
本站无任何商业行为
个人在线分享 » Web 网页性能优化
E-->
© 2018 Theme by - 本站无任何商业行为 & WordPress Theme. All rights reserved 蒙ICP备2023002302号-2