进阶 01 Web性能优化

很多资料表明,Web 的性能(加载速度、交互流畅度)优化对于提高用户留存、转化率等都有积极影响。

  • 来自Google的数据表明,一个有10条数据0.4秒能加载完的页面,变成30条数据0.9秒加载完之后,流量和广告收入下降90%。

  • Google Map 首页文件大小从 100KB 减小到 70-80KB 后,流量在第一周涨了10%,接下来的三周涨了25%。

  • 亚马逊的数据表明:加载时间增加100毫秒,销量就下降1%。

Web 的性能优化可以分为 web 前端性能优化应用服务器端性能优化存储服务器端性能优化

Web 前端性能优化一直以来都是前端工程领域中的一个重要部分。作为 Web 前端开发者,我们不仅要关注 Web 前端性能优化,还应对于应用服务器端性能优化存储服务器端性能优化有所了解。比如:你不应当只了解 HTTP 缓存,网站的缓存策略还包括:DNS 缓存、客户端缓存、服务端缓存、数据库缓存。

什么是 web 性能?

Web 性能是客观的衡量标准,是用户对加载时间和运行时的直观体验,是指页面加载到可交互和可响应所消耗的时间,以及页面在交互时的流畅度——滚动是否顺滑?按钮能否点击?弹窗能否快速打开,动画是否平滑?

Web 性能既包括客观的度量如加载时间,每秒帧数和到页面可交互的时间;也包括用户的对页面内容加载时间的主观感觉。

  • 客观度量: 从发出请求开始,到下载、解析和执行所有资源以及最终绘制的整个过程的时间度量。

  • 主观感知: 用户对网站性能、响应能力和可靠性的主观衡量。换句话说,网站在用户看来有多快。主观感知的量化和衡量比客观度量更难,但也许也更重要。

相比网站实际加载和渲染的速度,网站可感知的加载和渲染的速度对用户体验的影响更大。即使操作需要很长时间(由于主线程的延迟或不可用),也可以通过显示加载微调器或一系列有用的提示和技巧来让用户在等待时保持参与(或笑话,或其他任何您认为合适的内容)。这种方法比什么都不显示要好得多,这会让人感觉它花费了更长的时间,并可能导致您的用户认为它已损坏并放弃。

作为 Web 开发者,我们可以优化客观性能,也可以在客观性能不够好时,提升用户的感知性能。

另外,用户体验除了良好的网站性能,还包括友好的异常处理、容错降级策略、无障碍能力、视觉体验、交互体验等等。

Web页面的内容是如何呈现的?

为了有效地了解 Web 性能、以及其背后的问题,我们应该了解浏览器是如何工作的。

工作的主要流程:

  • 导航: 加载 web 页面的第一步,可以是用户通过在地址栏输入一个URL、点击一个链接、提交表单或者是其他的行为。

  • 找缓存: 浏览器查找当前 URL 是否存在缓存,并检测缓存是否可用。

  • **DNS查找:**浏览器通过服务器名称请求 DNS 进行查找,最终返回一个 IP 地址。

  • TCP连接(三次握手):一旦获取到服务器IP地址,浏览器就会通过TCP”三次握手“与服务器建立连接。

  • **服务器响应:**浏览器代表用户发送一个初始的 HTTP GET 请求,对于网站来说,这个请求通常是一个HTML文件。一旦服务器收到请求,它将使用相关的响应头和 HTML 的内容进行回复。

  • 浏览器解析文档和渲染页面。

  • 关闭TCP连接(四次挥手)。

什么是关键渲染路径(CRP)?

**关键渲染路径(CRP,Critical Rendering Path)**是浏览器将 HTML,CSS 和 JavaScript 转换为屏幕上的像素所经历的步骤序列。优化关键渲染路径可提高渲染性能。关键渲染路径包含了 文档对象模型 (DOM),CSS 对象模型 (CSSOM),渲染树和布局。

在解析 HTML 时会创建文档对象模型。HTML 可以请求 JavaScript,而 JavaScript 反过来,又可以更改 DOM。HTML 包含或请求样式,依次来构建 CSS 对象模型。浏览器引擎将两者结合起来以创建渲染树。布局确定页面上所有内容的大小和位置。确定布局后,将像素绘制到屏幕上。

浏览器的渲染可分为以上5个步骤:

  • 处理 HTML 标记并构造 DOM 树;

  • 处理 CSS 并构建 CSSOM 树;

  • 将 DOM 和 CSSOM 组合成一个Render树;

  • 在渲染树上运行布局以计算每个节点的几何体;

  • 将各个节点绘制到屏幕上,第一次出现的节点称为首次有意义的绘制(FMP)

优化关键渲染路径即尽可能地缩短上述步骤耗费的总时间。

阻塞渲染的资源

  • HTML: HTML 显然是必需的,因为如果没有 DOM,我们就没有可渲染的内容。

    浏览器处理 HTML 流程:字节字符令牌节点对象模型,最终得到的是文档对象模型 (DOM)

    • 字节转换字符: 浏览器读取 HTML 原始字节,根据指定编码转化为字符;

    • 令牌化(字符转标签): 将字符串转换成 W3C HTML5 标准规定的各种令牌,例如:html、body

    • 词法分析(标签转node): 将 token 转化为定义其属性与规则的 node;

    • DOM 构建: 将 node 连接在一起组成一颗 DOM 树。

  • CSS: CSS 的必要性可能没那么明显。但可以想像下,不阻塞 css 会发生的情况:一、渲染最先渲染出来的页面缺少样式(丑,而且可能影响一些交互功能),通常称为 内容样式短暂失效 (FOUC,flash of unstyled content);二、css 是可层叠的,当同一 DOM 在不同的 css 文件有定义样式时,每次加载完一个 css 资源都需重新绘制,可能会导致页面渲染闪动。

    所以,默认情况下,CSS 被视为阻塞渲染的资源,这意味着浏览器将不会渲染任何已处理的内容,直至 CSSOM 构建完毕。

    浏览器处理 CSS 流程 与 HTML 相似,最终得到的是CSS对象模型 (CSSOM)

    • CSS 会阻塞页面的渲染;

    • CSS 会阻塞 Javascript 执行,因为 JavaScript 可能会查询或者修改样式;

    • 动态插入的外链 CSS 不会阻塞 DOM 的解析或渲染,即用 Js 插入 link 标签;

    • 动态插入的内联 CSS 会阻塞 DOM 的解析或渲染,即 CSS 文件中用 @import 指令导入另一 css 文件。

  • **JavaScript:**JavaScript 允许修改网页的方方面面: 内容、样式以及它如何响应用户交互。 不过,JavaScript 也会阻止 DOM 构建和延缓网页渲染。因此,通常将 JavaScript 文件放在底部。

    • 同步的 JavaScript 都会阻塞 DOM 的解析或渲染

    • 异步的 JavaScript 不会阻塞DOM 的解析或渲染(async 与 defer 的 JavaScript 脚本、动态插入的外链 JavaScript 脚本)

优化关键渲染路径在很大程度上是指了解和优化 HTML、CSS 和 JavaScript 之间的依赖关系谱。

优化关键渲染路径

通过优化关键渲染路径,我们可以显著缩短首次渲染页面的时间。 此外,了解关键渲染路径还可以为构建高性能交互式应用打下基础。处理交互式更新的过程是相同的,只是在连续循环中完成,理想情况下每秒可以处理 60 帧!

为尽快完成首次渲染,我们需要最大限度减小以下三种可变因素:

  • 关键资源: 可能阻止网页首次渲染的资源;

  • 关键路径长度: 获取所有关键资源所需的往返次数或总时间;

  • 关键字节: 实现网页首次渲染所需的总字节数,它是所有关键资源传送文件大小的总和。

**优化关键渲染路径的常规步骤如下: **

  • 对关键路径进行分析和特性描述: 资源数、字节数、长度。

  • 最大限度减少关键资源的数量(减少往返次数): 删除它们、延迟它们的下载、将它们标记为异步等。

  • 优化关键字节数以缩短下载时间。

  • 优化其余关键资源的加载顺序: 您需要尽早下载所有关键资产,以缩短关键路径长度。

Web 性能的主要领域

  • 减少整体加载时间:呈现网站所需的文件需要多长时间才能下载到用户的计算机上?

    这往往会受到延迟、文件大小、文件数量以及其他因素的影响。一般策略是使文件尽可能小,尽可能减少发出的 HTTP 请求的数量,并采用巧妙的加载技术(例如 preload)使文件更快可用。

  • 使网站尽快可用:以合理的顺序加载网站资源,让用户可以真正快速地开始实际使用它。

    当用户继续执行主要任务时,任何其他资源都可以在后台继续加载,有时我们只在实际需要时才加载资源(这称为延迟加载)。

  • 流畅性和交互性:应用程序使用起来是否可靠和愉快?滚动是否流畅?按钮是否可点击?弹出窗口是否可以快速打开,并且在打开时动画流畅?

    要使应用程序感觉流畅,需要考虑许多最佳实践,例如:使用 CSS 动画而不是 JavaScript 来制作动画,以及最大限度地减少由于 DOM 的变化而导致的 UI 重绘次数。

  • 感知性能:网站在用户看来的速度比网站实际速度对用户体验的影响更大。

    用户如何看待您的表现与任何客观统计数据一样重要,甚至可能更重要,但它是主观的,不容易衡量。感知性能是用户视角,而不是指标。即使操作需要很长时间(由于延迟或其他原因),也可以通过显示加载微调器或一系列有用的提示和技巧(或笑话,或其他任何内容)来保持用户的参与度否则你认为可能是合适的)。这种方法比什么都不显示要好得多,这会让人感觉它花费了更长的时间,并可能导致您的用户认为它已损坏并放弃。

  • 性能测量:Web 性能涉及测量应用程序的实际速度和感知速度,在可能的情况下进行优化,然后监控性能,以确保您优化的内容保持优化。

总而言之,许多特性会影响性能,包括延迟、应用程序大小、DOM 节点数量、资源请求数量、JavaScript 性能、CPU 负载等。重要的是尽量减少加载和响应时间,并通过尽可能快地使体验尽可能可用和交互,同时在体验的较长尾部异步加载来添加附加功能以隐藏延迟。

web 性能测量指标

为了能衡量用户视觉体验,Web 标准中定义了一些性能指标,这些性能指标被各大浏览器标准化实现,例如 First PaintFirst Contentful Paint。还有一些由 Web 孵化器社区组(WICG)提出的性能指标,如 Largest Contentful Paint 、``Time to InteractiveFirst Input DelayFirst CPU Idle。另外还有 Google 提出的 First Meaningful PaintSpeed Index,百度提出的 First Screen Paint`。

这些指标之间并不是毫无关联,而是在以用户为中心的目标中不断演进出来的,有的已经不再建议使用、有的被各种测试工具实现、有的则可以作为通用标准,有各大浏览器提供的可用于在生产环境测量的 API。

指标可分为三类:文档加载相关内容呈现相关交互响应性相关

文档加载相关

  • 首次接收字节(TTFB,Time to First Byte):浏览器从请求页面开始到接收第一字节的时间,这个时间段内包括 DNS 查找、TCP 连接和 SSL 连接。

  • 文档加载完成(DCL,DomContentLoaded)DOMContentLoaded 事件触发时间。当 HTML 文档被完全加载和解析完成之后,DOMContentLoaded 事件被触发,而无需等待样表、图像和子框架加载完成。

  • 页面加载完成(Load)onLoad 事件触发的时间。页面所有资源都加载完毕后(比如图片,CSS),onLoad 事件才被触发。

内容呈现相关

  • 首次绘制(FP,First Paint):由 Web 性能工作组在 W3C 标准 Paint Timing 中提出。

    从开始加载到浏览器首次绘制像素到屏幕上的时间,也就是页面在屏幕上首次发生视觉变化的时间。请注意,此绘制可能不可见;它可以是简单的背景颜色更新,也可以是更不引人注目的东西。

  • 首次内容绘制(FCP,First Contentful Paint):由 Web 性能工作组在 W3C 标准 Paint Timing 中提出。

    浏览器首次绘制来自 DOM 的内容的时间,内容必须是文本、图片(包含背景图)、非白色的 canvas 或 SVG,也包括带有正在加载中的 Web 字体的文本。

    请注意,这是用户第一次开始看到页面内容,但不一定有用或有意义(例如 Header、导航栏等)。

  • 首次有意义的绘制(FMP,First Meaningful Paint)(非标准):由 Google 在 Time to First Meaningful Paint: a layout-based approach 中提出。

    页面的主要内容绘制到屏幕上的时间,这是一个更好的衡量用户感知加载体验的指标,但仍然不理想。

    主要内容的定义因页面而异。例如对于博客文章,它的主要内容是标题和摘要;对于搜索页面,它的主要内容是搜索结果;对于电商的页面,图片则是主要内容。

    计算方式: 采用 布局数量最大并且 Web 字体已加载 的时刻作为近似时间。

    通过一种计算布局对象数量的方式来估算的,该算法将(添加到布局树的布局对象数 / max(1, 页面高度/屏幕高度))最大的时刻作为 FMP 的时间点,如果在布局时正在加载字体,则布局变动时间将推迟到显示字体为止,以此来猜测页面的主要内容绘制到屏幕中的时间。

    这种计算方式对页面加载的微小差异过于敏感,容易导致结果不一致。此外,度量的定义依赖于特定于浏览器的实现细节,这意味着它不能标准化,也不能在所有 Web 浏览器中实现。

    在 Lighthouse 6.0 中已不推荐使用 FMP,建议使用 Largest Contentful Paint 代替。

  • **最大内容绘制(LCP,Largest Contentful Paint)(非标准):**由 Web 孵化器社区组(WICG)在 Largest Contentful Paint API 中提出。

    可视区域中最大的内容元素呈现到屏幕上的时间,用以估算页面的主要内容对用户可见时间。

    关于最大内容元素的计算可以查阅 Largest Contentful Paint 规范,此规范提供了 API 可以获取 LCP 时间(如果浏览器实现了此 API 的话,Chrome 浏览器是实现了的)。

  • 速度指数(SI,Speed Index)(非标准):由 Google 在 webpagetest.org 中提出。

    测量可见屏幕上要绘制的像素的平均时间。

  • 首屏绘制(FSP,First Screen Paint)(非标准):由百度在 W3C 标准提案 First Screen Paint 中提出。

    页面从开始加载到首屏内容全部绘制完成的时间,用户可以看到首屏的全部内容。

    如果说 LCP 是用户看到有效内容的最近似的时间,那么在 FSP 这个时间点用户已经看到了可视区域内完整的内容,可以说是衡量用户视觉体验最合适的指标

交互响应性相关

  • 可交互时间(TTI,Time to Interactive)(非标准):由 Web 孵化器社区组(WICG)在 Time To Interactive 中提出。

    表示网页第一次 完全达到可交互状态 的时间点,浏览器已经可以持续性的响应用户的输入。

  • 首次CPU空闲时间(FCI,First CPU Idle)(非标准):由 Web 孵化器社区小组提出的 First Interactive 指标,并已被用于各种工具中。这个指标在 LightHouse 中称为 First CPU Idle(FCI)

    页面第一次可以响应用户输入的时间。

    FCI 和 TTI 都是页面可以响应用户输入的时间。FCI 发生在用户可以开始与页面交互时;TTI 发生在**用户完全能够(可持续)**与页面交互时。第一次可交互与可流畅交互的时间点如何确定可以在 Google 的 First Interactive and Consistently Interactive 中查阅。

  • 首次输入延迟 (FID,First Input Delay)(非标准):此指标由网络孵化器社区小组(WICG)提出,并已被用于各种工具中。

    从用户第一次与页面交互(例如单击链接、点击按钮等)到浏览器实际能够响应该交互的时间。

    输入延迟是因为浏览器的主线程正忙于做其他事情,所以不能响应用户。发生这种情况的一个常见原因是浏览器正忙于解析和执行应用程序加载的大型 JavaScript 文件。

    第一次输入延迟通常发生在第一次内容绘制(FCP)和可持续交互时间(TTI)之间,因为页面已经呈现了一些内容,但还不能可靠地交互。

性能优化策略

加载优化策略(14)

  • 减少 HTTP 请求

    Web 前端 80% 的响应时间花在图片、样式、脚本等资源下载上,减少资源下载时间是性能优化的黄金法则。最直接的方式是减少页面所需资源。

  • 静态资源使用 CDN

    内容分发网络(CDN)是一组分散在不同地理位置的 web 服务器,用来给用户更高效地发送内容。典型地,选择用来发送内容的服务器是基于网络距离的衡量标准的。服务器离用户越远,延迟越高。

  • 使用 Gzip 压缩

    通过 Gzip 压缩 HTML 文档、脚本、样式表(图片、 PDF 文件不应该被压缩,因为它们本来已经被压缩了),减少 HTTP 请求产生的响应包的大小(通常能将响应的数据量减少近 70%),从而降低传输时间,提高性能。

    Web 客户端可以通过 HTTP 请求 Header 中的 Accept-Encoding 来标识对压缩的支持:

    Content-Encoding: gzip, deflate, br

    Web 服务器看到请求中的 Header,就会使用客户端列出的方法中的一种来压缩响应。Web 服务器通过响应 Header 中的 Content-Encoding 来告知 Web 客户端。

    Content-Encoding: gzip
  • 减少 DNS 查询

    用户输入 URL 以后,浏览器首先要查询域名对应服务器的 IP 地址,这个操作一般需要耗费 20-120 毫秒时间。DNS 查询完成之前,浏览器无法从服务器下载任何数据。

    基于性能考虑,ISP、局域网、操作系统、浏览器都会有相应的 DNS 缓存机制。

    • IE 缓存 30 分钟,可以通过注册表中 DnsCacheTimeout 项设置;

    • Firefox 缓存 1 分钟,通过 about:config -> network.dnsCacheExpiration 配置;

    • Chrome 缓存 1 分钟,通过 chrome://net-internals/#dns 配置。

    注意:

    同一时间针对同一域名下的请求有一定数量限制,超过限制数目的请求会被阻塞。大多数浏览器的并发数量都控制在6以内。有些资源的请求时间很长,因而会阻塞其他资源的请求。因此,对于一些静态资源,如果放到不同的域名下面就能实现与其他资源的并发请求。

    减少不同的域名可减少 DNS 查找,同时也减少了页面下载资源文件的并发量。也就是说,虽然避免 DNS 查找削减了响应时间,但是减少并行下载数量却增加了响应时间。

    原则是把组件分散在 2~4 个域名下,控制好数量,这是同时减少 DNS 查找和允许并发下载的折中方案。

  • DNS预解析

    尝试在请求资源之前解析域名。浏览器在获取域名的实际 IP 地址后,会对其 IP 进行缓存,减少网络请求的损耗。该域名可能是后面要加载的文件,也可能是用户尝试打开的链接目标。

    <link rel="dns-prefetch" href="https://fonts.googleapis.com/"> 
  • 预先建立连接

    建立连接不仅需要 DNS 查询,还需要进行 TCP 协议握手,有些还会有 TLS/SSL 协议,这些都会导致连接的耗时。

    <link rel="preconnect" href="//sample.com">

    使用 Preconnect 时,浏览器大致做了如下处理:

    • 解析 Preconnect 的 url;

    • 根据当前 link 元素中的属性进行 cors 的设置;

    • 默认先将 credential 设为 true,如果 cors 为 Anonymous 并且存在跨域,则将 credential 置为 false;

    • 进行连接。

  • 使用多个域名

    Chrome 等现代化浏览器,都会有同域名限制并发下载数的情况,使用不同的域名可以最大化下载线程。

    浏览器的并发限制:

    • Chrome34/32:6

    • IE10:8

    • IE11:13

    • Firefox27/26:6

    • Safari7.0.1:6

  • Ajax 时尽量使用 GET 方法

    使用 XMLHttpRequest 时,浏览器的 POST 请求是通过一个两步的过程来实现的:先发送 HTTP 头,再发送数据。所以最好用 GET 请求,它只需要发送一个 TCP 报文(除非 Cookie 特别多)。

  • 延迟加载

    • 将不影响首屏的资源和当前屏幕不用的资源放到用户需要时才加载(懒加载)。如:非首屏使用的数据、样式、脚本、图片等;用户交互时才会显示的内容。

  • 图片的延迟加载(滚屏加载)。如:当用户滚动页面看到内容时,再加载所需图片。

  • 预加载

    预加载是可以充分利用浏览器空闲的时间,来请求将来会用到的资源(图片,样式和脚本等)。

      <link rel="preload" href="fonts/webfont.woff2" as="font" type="font/woff2" crossorigin>

    crossorigin 属性是不可少的,即便加载的资源文件与 HTML 资源是同源的。

  • 给 Cookie 减肥

    Cookie 通过 HTTP 头在服务器和浏览器间来回传送,减少 Cookie 大小可以降低其对响应速度的影响。

    **优化指南:**去除不必要的 Cookie;尽量压缩 Cookie 大小;注意设置 Cookie 的 domain 级别,如无必要,不要影响到 sub-domain;设置合适的过期时间。

  • 静态资源域名不使用 Cookie

    当浏览器请求静态图片并把 Cookie 一起发送到服务器时,Cookie 此时对服务器没什么用处,只是增加了网络流量。

    通常可以创建一个子域名来托管所有静态组件。

  • 避免 301/302 重定向

    客户端收到服务器的重定向响应后,会根据响应头中 Location 的地址再次发送请求。重定向会影响用户体验,尤其是多次重定向时,用户在一段时间内看不到任何内容,只看到浏览器进度条一直在刷新。

    常见错误:URL 末尾应该添加 / ,但未添加。

  • 杜绝 404

    HTTP 请求是昂贵的,所以发出 HTTP 请求但获得没用的响应(如 404)是完全不必要的,并且会降低用户体验。

    此外,如果链接指向外部 js,但却得到 404 结果。这样首先会降低(占用)并行下载数,其次浏览器可能会把 404 响应体当作 js 来解析。

缓存优化策略(3)

  • HTTP 缓存:尽可能的利用所有可用的缓存资源。

    • keep-alive:减少 Initial connection 耗时;

    • Expires 或 Cache-Control(强缓存),设置资源是否缓存,以及缓存时间;

    • Etag / If-None-Match(协商缓存):通过对比资源唯一标识来决定要不要从服务器获取资源。

  • Last-Modified / If-Modified-Since(协商缓存):通过对比时间来决定要不要从服务器获取资源。

  • 使用外部 JavaScript 和 Css

    使用外部文件通常会产生较快的页面,因为 JavaScript 和 CSS 有机会被浏览器缓存起来。对于内联的情况,由于 HTML 文档通常不会被配置为可以进行缓存的,所以每次请求 HTML 文档都要下载 JavaScript 和 CSS。

  • 缓存 Ajax 请求

资源优化策略(8)

  • 确保代码简洁:去删无用代码,避免重复代码,提高代码复用率;

  • 代码合并(CSS,JS):将多个小的资源文件,合成一个;

  • 压缩资源文件(HTML,CSS,JS,IMG等):压缩文件可以减少文件下载时间;

    对 HTML、css、js 来说,就是从代码中去除不必要的字符(所有注释和不必要的空白字符:空格、换行、tab)以减少大小,从而提升加载速度。在 webpack 可以使用如下插件进行压缩:

    • JavaScript:UglifyPlugin

  • CSS :MiniCssExtractPlugin

    • HTML:HtmlWebpackPlugin

    对 IMG 来说,可能通过 tinypng.com 网站,或者其他图片压缩工具来减少文件大小。

  • HTML

    • 减少 DOM 节点: DOM 节点太多影响页面的渲染,尽量减少 DOM 节点。

    • 避免在 HTML 中书写 style;

    • 避免使用 iframe。加载代价昂贵,即使是空的页面;阻塞页面 load 事件触发,Iframe 完全加载以后,父页面才会触发 load 事件。

  • Javascript

    • 避免 Long Task;

  • 优化 polyfill 的使用:只加载真正所需的 polyfill ;

    • 是否真的需要框架。如: React 技术栈 —— 你加载了 React、Redux、React-Redux、一堆 Reducers…… 好吧,整个 JavaScript 可能快 1MB 了。对于一个静态页(或者还有一些非常简单的表单交互),是否真的需要引用框架?

    • 减少 DOM 操作。如:缓存已经访问过的元素;使用 DocumentFragment 暂存 DOM,整理好以后再插入 DOM 树;使用 className 来操纵元素的样式;避免使用 JavaScript 修复布局。

    • 使用高效的事件处理。如:使用事件委托;DOM 操作尽量在 DOMContentLoaded 中, DOMContentLoadedonload 事件更快。

  • Css

    • 选择合适的兼容性。如:Autoprefixer 的版本指定。

    • 使用选用的布局方式:不滥用 float、table 布局,它们在渲染时计算量比较大,新版的 flex 进行布局性能更好。

    • 使用 <link> 替代 @import

    • 降低 CSS 选择器的复杂性:浏览器读取选择器,遵循的原则是从选择器的右边到左边读取。如:

      #block .text p {
      	color: red;
      }

      查找流程:查所有 P 元素 -> P 元素中类名为 text 的父元素 -> .text 元素中 id 为 block 的父元素。

      注:实际 CSS 选择器没有优化的必要,因为最慢和慢快的选择器性能差别非常小。

    • 避免使用通配** 是针对 CSS 中所有选择器,比较耗性能,尽量使用更具体的选择器。

    • 避免使用 CSS 表达式。因为,有些表达式可能会高频触发,比如:页面滚动或鼠标移动。

      background-color: expression( (new Date()).getHours()%2 ? "#B8D4FF" : "#F08A00" );
    • 不滥用 Web 字体:Web字体需要下载、解析、重绘当前页面,尽量减少使用;

    • 值为 0 时不需要任何单位:为了浏览器的兼容性和性能;

    • 属性值为浮动小数 0.**,可以省略小数点之前的 0;

  • 图片

    • 尽可能使用 CSS3、SVG、IconFont 代替图片;

    • 雪碧图:将多个小图标合并为一张大图,减少请求数;

    • 使用合适的图片格式。如:图片本身应该是 100*100px 的,不要缩小 500*500px 的图片来用。

    • 图像尽量避免使用 DataURL:DataURL 图像没有使用图像的压缩算法,文件会变大,并且要解码后再渲染,加载慢耗时长

    • 图片质量的权衡。在大多数情况下,相较于 100 质量系数的压缩,80~85 的质量系数可以带来 30~40% 的大小缩减,同时对图片效果影响较小,即人眼不易分辨出质量效果的差异。

    • 响应式图片:即浏览器能够根据屏幕大小自动加载合适的图片。

      <picture>
          <source srcset="banner_w1000.jpg" media="(min-width: 801px)">
          <source srcset="banner_w800.jpg" media="(max-width: 800px)">
          <img src="banner_w800.jpg" alt="">
      </picture>
    • 使用合适的图片格式:尽量使用 webp 格式的图片,webp 优于 jpg/png。

    • 尽量避免重置图像大小:多次重置图像大小会引发图像的多次重绘,影响性能。

    • 避免图片 src 为空:src 属性为空字符串,但浏览器仍然会向服务器发起一个 HTTP 请求。

  • 其他资源

    • 使用字体图标 iconfont 代替图片图标;

    • 使用体积小、可缓存的 favicon.ico。避免 404,尽量小,最好小于 1K,并且设置较长的过期时间。

渲染优化策略(10)

  • CSS 文件放在头部

  • JavaScript 文件放在底部

  • 优化关键渲染路径

  • 使用服务端渲染

    客户端渲染: 获取 HTML 文件,根据需要下载 JavaScript 文件,运行文件,生成 DOM,再渲染。

    服务端渲染: HTML 是通过服务端渲染生成的,浏览器只需解析和渲染服务端返回的 HTML 就可以了。

  • 减少重流重绘

  • 优化高频事件scrolltouchmove等事件可导致多次渲染

    • 函数节流。限制函数一段时间内只能执行一次。

      function throttle(fn, delay) {
          let timer
          return function () {
              let _this = this
              let args = arguments
              if (timer) {
                  return
              }
              timer = setTimeout(function () {
                  fn.apply(_this, args)
                  timer = null
              }, delay)
          }
      }
    • 函数防抖。在事件被触发 n 秒后再执行回调,如果在这n秒内又被触发,则重新计时。

      function debounce(fn, delay) {
          let timer
          return function () {
              let _this = this
              let args = arguments
              if (timer) {
                  clearTimeout(timer)
              }
              timer = setTimeout(function () {
                  fn.apply(_this, args)
              }, delay)
          }
      }
    • 使用 requestAnimationFrame 监听帧变化

    • 增加响应变化的时间间隔

  • GPU加速:使用某些 HTML5 标签和 CSS3 属性会触发 GPU渲染,请合理使用(过渡使用会引发手机耗电量增加)。

    • HTML标签:videocanvaswebgl

    • CSS属性:opacitytransformtransition

  • 优化动画

    • 尽量使用 CSS3 动画

    • 使用 requestAnimationFrame 来实现视觉变化。

      在使用 JavaScript 实现动画效果的时候,最好的情况就是每次代码都是在帧的开头开始执行。而保证 JavaScript 在帧开始时运行的唯一方式是使用 requestAnimationFrame

    • 适当使用 Canvas 动画:5个元素以内使用 CSS 动画,5个元素以上使用 Canvas 动画,iOS8+ 可使用 WebGL 动画。

  • 设置 viewport: HTML 的 viewport 可加速页面的渲染。

    <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1, minimum-scale=1, maximum-scale=1">
  • 避免页面卡顿

    大多数设备的屏幕刷新率为 60 次/秒。因此,如果在页面中有一个动画或渐变效果,或者用户正在滚动页面,那么浏览器渲染动画或页面的每一帧的速率也需要跟设备屏幕的刷新率保持一致。

    每个帧的预算时间仅比 16 毫秒多一点 (1 秒/ 60 = 16.66 毫秒)。但实际上,浏览器有整理工作要做,因此您的所有工作需要在 10 毫秒内完成

    如果无法符合此预算,帧率将下降,并且内容会在屏幕上抖动。 此现象通常称为卡顿,会对用户体验产生负面影响。

前端优化的一些常见规则

3秒首屏指标

此规则顾名思义就是打开页面后3秒钟内完成渲染并展示内容。

2-5-8原则

在前端开发中,此规则作为一种开发指导思路,针对浏览器页面的性能优化。

  • 用户在2秒内得到响应,会感觉页面的响应速度很快 Fast;

  • 用户在2~5秒间得到响应,会感觉页面的响应速度还行 Medium;

  • 用户在5~8秒间得到响应,会感觉页面的响应速度很慢,但还可以接受 Slow;

  • 用户在8秒后仍然无法得到响应,会感觉页面有问题。

雅虎军规

雅虎团队通过大量实践总结出以下7类35条前端优化规则:

相关链接

Web 性能 MDN

关键渲染路径 MND

优化关键渲染路径

35 条前端性能优化军规

前端性能优化指南[1]--前言

前端性能优化

前端性能优化方法与实战

最后更新于