页面性能优化

607 阅读13分钟
  1. 当我们谈论页面性能优化的时候,我们在谈什么?

    谈如何让页面更快地显示和响应。通常一个页面有三个阶段:加载阶段、交互阶段和关闭阶段:

    • 加载阶段指从发出请求到渲染出完整页面的过程,影响这个阶段的主要因素有网络和 JavaScript 脚本。
    • 交互阶段指从页面加载完成到用户交互的整个过程,影响这个阶段的主要因素是 JavaScript 脚本。
    • 关闭阶段指用户发出关闭指令后页面所做的一些清理操作。

    影响用户体验的因素主要在加载阶段交互阶段

  2. 加载阶段:从输入 URL 到渲染出完整页面中间发生了什么?

    从输入 URL 到渲染出完整页面经历了导航和渲染两个阶段。

    (1) 导航是指用户发出URL请求到页面开始解析的这个过程。

    1. 用户输入】用户输入关键字,按下回车;
    2. 发送URL请求】浏览器进程通过进程间通信(IPC)把 URL 请求发送至网络进程;
    3. 查找缓存 / DNS解析 / 建立TCP连接】网络进程查找本地是否缓存了该资源,是则返回该资源给浏览器进程,否则进行DNS解析,获取请求域名的IP地址,然后和服务器建立TCP连接;
    4. 发送请求信息】浏览器构建请求行、请求头等信息,并把和该域名相关的Cookie等数据附加到请求头中,发送给服务器。服务器根据请求信息生成响应数据,发给网络进程;
    5. 解析响应头】网络进程解析响应头,如果返回的状态码是301302,就从响应头的Location字段读取重定向地址,然后再发起新的请求。如果返回的状态码是 200,浏览器就继续处理响应数据;
    6. 处理响应数据 / 准备渲染进程】浏览器根据响应头中的Content-Type值决定如何显示响应体的内容,如果Content-Type值为text/html,则服务器返回的数据是HTML格式,浏览器就准备渲染进程(注:通常情况下,打开新的页面都会使用单独的渲染进程;但如果从 A 页面打开 B 页面,而 A 和 B 属于同一站点,B 页面就会复用 A 页面的渲染进程,否则浏览器进程会为 B 创建一个新的渲染进程);
    7. 提交文档】渲染进程准备好后,浏览器进程向渲染进程发出“提交文档”的消息,渲染进程收到消息后和网络进程建立传输数据的“管道”。数据传输完成后,渲染进程返回“确认提交”的消息给浏览器进程,浏览器进程更新界面状态,包括安全状态、地址栏的 URL、前进后退的历史状态,并更新Web页面。

    (2) 渲染阶段在执行过程中会被划分为很多子阶段,输入的HTML经过这些子阶段,最后生成像素。

    1. 构建DOM树】浏览器通过HTML解析器将HTML文件转换为**DOM树**;
    2. 样式计算】渲染引擎将CSS文本转换为styleSheets,计算出DOM节点的具体样式;
    3. 布局阶段】构建一棵只包含可见元素的布局树,然后计算布局树节点的坐标位置;
    4. 分层】对布局树进行分层,并生成分层树。(注:通常情况下,拥有层叠上下文属性的元素和需要剪裁的地方会被创建为单独的图层,没有对应图层的节点从属于父节点的图层);
    5. 图层绘制】渲染引擎为每个图层生成绘制列表,并将其提交给合成线程;
    6. 栅格化操作】合成线程将图层划分为图块,并在栅格化线程池中将图块转换为位图;(使用 GPU )
    7. 合成和显示】合成线程提交“DrawQuad”的命令给浏览器进程,浏览器将页面内容绘制到内存中,最后再将内存显示在屏幕上。
  3. 如果下载 CSS 文件阻塞了,会阻塞 DOM 树的合成吗?会阻塞页面的显示吗?

    有可能会。浏览器接收到HTML文件后开始解析,在解析过程中如果遇到了内联JS脚本,解析器会先执行JS脚本,执行完后再继续解析。如果遇到了外部JS文件,会先暂停解析,下载和执行完JS文件后,再继续解析。如果执行JS时,在JS中访问了某个元素的样式,就需要等待这个样式被下载之后才能继续往下执行,这种情况下CSS会阻塞DOM树的合成。

  4. 什么是重排、重绘和合成?

    • 修改元素的几何位置属性,浏览器会触发重新布局,这个过程叫重排,重排需要更新完整的渲染流水线。
    • 如果只修改元素的背景颜色,布局阶段不会被执行,直接进入绘制阶段,这个过程叫重绘,重绘相较于重排,省去了布局和分层阶段,执行效率会高一些。
    • 如果使用CSStransform实现动画效果,可以直接在合成线程上执行动画操作,这样可以避开布局、分层和绘制阶段,提高执行效率。
  5. 如何减少重排和重绘?

    1. 分离DOM属性的读写操作,避免多次强制刷新

    2. 样式集中改变,通过改变classcssText属性集中改变样式

    3. 缓存布局信息,相当于读写分离

    4. 离线改变DOM

      • 通过display隐藏要改变的DOM,操作完成之后,再将DOMdisplay设为可见
      • 使用DocumentFragment创建一个DOM碎片,在它上面批量操作DOM,操作完成后,再添加到文档中
      • 复制节点,在副本上操作,然后替换原来的节点
    5. 将需要多次重排的元素的position属性设置为absolutefixed

    6. 优化动画

      • 把动画效果应用到position属性为absolutefixed的元素上

      • 启用GPU加速:

        GPU 是专门为处理图形而设计,可以把浏览器中的一些图形操作交给 GPU 来完成。

        GPU 加速通常包括以下几个部分:Canvas2D,布局合成,CSS3转换( transitions ),CSS3 3D变换( transforms ),WebGL和视频( video )。

    7. 使用虚拟DOM计算出操作总的差异,一起提交给浏览器

  6. 针对加载中的导航阶段可以做哪些优化工作?每一项优化工作是解决什么问题?要达到怎样的效果?

    我们把能阻塞网页首次渲染的资源称为关键资源,对于关键资源,可以做出以下优化:

    1. 减少关键资源个数
      • 合并脚本和样式表
      • 如果JS中不涉及DOMstyleSheet操作,可以加上asyncdefer属性,将其变成非关键资源
      • 不建议内联样式或脚本,不利于缓存
    2. 降低关键资源的大小
      • 压缩HTMLCSSJS代码
      • 移除HTMLCSSJS中的无用代码
    3. 减少关键资源RTT(数据包往返时延)的次数
      • 减少关键资源个数
      • 合理搭配关键资源的大小
      • 使用CDN

    非关键资源可做出以下优化:

    1. 减少HTTP请求

      • 图片地图
      • CSS Sprites
      • 内联图片
    2. 懒加载

      将页面上图片的src属性置为空字符串,图片的真实路径设置在data-original属性中,页面滚动的时候监听scroll事件,在scroll事件的事件处理程序中,判断懒加载的图片是否进入可视区域,是则将图片的src属性设置为data-original的值,这样就实现了延迟加载。

    3. 预加载

      将所需的资源提前请求加载到本地,在需要用到时就直接从缓存取资源。

    4. DNS预解析

    非首次加载:使用缓存

  7. 针对加载中的渲染阶段可以做哪些优化工作?每一项优化工作是解决什么问题?要达到怎样的效果?

    1. 将样式表放在顶部
      • 避免 FOUC 问题(无样式内容的闪烁:如果样式表被放在底部,当页面逐步加载时,文字首先显示,然后是图片。最后,在样式表正确地下载并解析之后,已经呈现的文字和图片要用新的样式重绘。浏览器选择白屏作为对 FOUC 问题的弥补,即使用样式表时,阻止页面的逐步呈现)
      • 缩短页面白屏时间
    2. 将脚本放在底部
      • 原因:使用脚本时,所有位于脚本以下的内容,逐步呈现都被阻塞了。将脚本放在页面越靠下的地方,越多的内容能够逐步呈现。
  8. 交互阶段的优化方法

    交互阶段通常是由 JS 修改 DOM 或者 CSSOM 触发生成一个新的帧,没有加载关键资源、构建 DOM 和CSSOM 的流程。所以交互阶段的优化主要是指优化渲染进程渲染帧的速度。

    1. 减少 JavaScript 脚本执行时间,避免执行过久占用主线程执行其他渲染任务的时间

      • 将一次执行的函数分解为多个任务
      • 采用 Web Workers 执行与 DOM 操作无关且耗时的任务
    2. 避免强制同步布局

      • 通常情况下,浏览器会把触发重排或重绘的操作放进渲染队列,然后批量处理。但是读取元素的 offsetTop、offHeight 等操作会导致浏览器强制同步布局,即:JavaScript强制将计算样式和布局操作提前到当前的任务中。
      • 可以在修改DOM之前查询相关值,避免强制同步布局
    3. 避免布局抖动

      • 布局抖动是指在一次 JavaScript 执行过程中,多次执行强制布局和抖动操作(多次读写 offsetTop 等属性)
      • 可以在修改 DOM 之前查询相关值,避免强制同步布局
    4. 合理利用 CSS 合成动画

      • 使用 CSS 合成动画,合成动画在合成线程上执行,不用占用主线程
      • 如果提前知道要对某个元素执行动画操作,可以将其标记为 will-change,即告诉浏览器需要将该元素单独生成一个图层
    5. 避免频繁的垃圾回收

      • 尽可能优化存储结构,尽可能避免小颗粒对象的产生
    6. 减少重绘和重排


详解:

  1. Chrome浏览器中有哪些进程?

    1个浏览器进程,1个GPU进程,1个网络进程、多个渲染进程和多个插件进程。

    1. 浏览器进程:负责界面展示、用户交互、子进程管理,同时提供存储等功能。

    2. 渲染进程:将HTML、CSS、JavaScript转换为网页。处于安全考虑,渲染进程都是运行在沙箱模式之下。

      • 主线程需要做的任务包括:运行JavaScript、计算HTML元素的CSS样式、layout (relayout)、把绘制列表提交给合成线程
      • 合成线程主要任务包括:将图层划分为图块,利用GPU生成位图,将位图绘制到屏幕上;计算哪部分页面是可见的、哪部分页面是即将可见的(当你滚动页面的时候)、在你滚动时移动部分页面
    3. GPU进程:生成位图,绘制页面。

    4. 网络进程:主要负责页面的网络资源加载。

    5. 插件进程:主要负责插件的运行。

  2. 如果下载 CSS 文件阻塞了,会阻塞 DOM 树的合成吗?会阻塞页面的显示吗?

    当从服务器接收HTML页面的第一批数据时,DOM解析器就开始工作了,在解析过程中,如果遇到了JS脚本,如下所示:
    <html>
        <body>
            极客时间
            <script>
            document.write("--foo")
            </script>
        </body>
    </html>
    那么DOM解析器会先执行JavaScript脚本,执行完成之后,再继续往下解析。
    
    那么第二种情况复杂点了,我们内联的脚本替换成js外部文件,如下所示:
    <html>
        <body>
            极客时间
            <script type="text/javascript" src="foo.js"></script>
        </body>
    </html>
    这种情况下,当解析到JavaScript的时候,会先暂停DOM解析,并下载foo.js文件,下载完成之后执行该段JS文件,然后再继续往下解析DOM。这就是JavaScript文件为什么会阻塞DOM渲染。
    
    我们再看第三种情况,还是看下面代码:
    <html>
        <head>
            <style type="text/css" src = "theme.css" />
        </head>
        <body>
            <p>极客时间</p>
            <script>
                let e = document.getElementsByTagName('p')[0]
                e.style.color = 'blue'
            </script>
        </body>
    </html>
    当我在JavaScript中访问了某个元素的样式,那么这时候就需要等待这个样式被下载完成才能继续往下执行,所以在这种情况下,CSS也会阻塞DOM的解析。
    
  3. 有了http2.0实现推送,还需要减少http请求吗?

    需要。http2.0使用服务器推送可以提高性能,但是提升程度也不是特别多,大概是几百毫秒。而且推送太多资源会拖累性能,因为浏览器不得不处理推送过来的资源,只推送CSS样式表可能是一个比较好的选择。

  4. 压缩HTMLCSSJS的方法

    方法:gzipdeflate

    配置:

    • 请求头:Accept-Encoding: gzip, deflate
    • 响应头:Content-Encoding: gzip

    压缩的成本有:服务端压缩文件、客户端对压缩文件进行解压缩。通常对大于1KB2KB的文件进行压缩。

  5. 什么是 CDN ?使用 CDN 的优缺点

    CDN 即内容发布网络,是一组分布在多个不同地理位置的 Web 服务器,用于更加有效地向用户发布内容。CDN 用于发布静态内容,如图片、脚本、样式表和 Flash。

    CDN 的优势包括:(1) 可以缩短响应时间;(2)备份;(3) 扩展存储能力;(4) 进行缓存;(5) 有助于缓和Web流量峰值压力。

    CDN 的缺点包括:(1) 响应时间可能受到其他网站流量的影响;(2) 无法直接控制组件服务器;(3) 应用程序的性能会受到CDN服务性能下降的影响。


参考:

  1. 【极客时间】浏览器工作原理与实践(4 / 5 / 6 / 25):time.geekbang.org/column/intr…
  2. 【掘金】浏览器重绘(repaint)重排(reflow)与优化:juejin.cn/post/684490…
  3. 【掘金】懒加载和预加载:juejin.cn/post/684490…
  4. 【掘金】页面性能优化办法有哪些?juejin.cn/post/684490…
  5. 【掘金】2018 前端性能优化清单:juejin.cn/post/684490…
  6. 高性能网站建设指南,Steve Souders
  7. CSS animation和transition的性能探究:zencode.in/18.CSS-anim…