我们所谈论的页面优化,其实就是要让页面更快地显示和响应
通常一个页面有三个阶段:加载阶段、交互阶段和关闭阶段。

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

    加载阶段

    image.png
    图片 音频 视频 等文件 就不会阻塞页面的首次渲染
    js 首次请求的 html css 文件是会阻塞首次渲染的,因为在构建 DOM 的过程中需要 HTML 和 JavaScript 文件,在构造渲染树的过程中需要用到 CSS 文件。
    这些能阻塞网页首次渲染的资源称为关键资源

  • 第一个是关键资源个数。关键资源个数越多,首次页面的加载时间就会越长。

  • 第二个是关键资源大小。通常情况下,所有关键资源的内容越小,其整个资源的下载时间也就越短,那么阻塞渲染的时间也就越短
  • 第三个是请求关键资源需要多少个 RTT(Round Trip Time)

RTT 就是这里的往返时延。它是网络中一个重要的性能指标,表示从发送端发送数据开始,到发送端收到来自接收端的确认,总共经历的时延。
通常 1 个 HTTP 的数据包在 14KB 左右,所以 1 个 0.1M 的页面就需要拆分成 8 个包来传输了,也就是说需要 8 个 RTT。
由于渲染引擎有一个预解析的线程,在接收到 HTML 数据之后,预解析线程会快速扫描 HTML 数据中的关键资源,一旦扫描到了,会立马发起请求,你可以认为 JavaScript 和 CSS 是同时发起请求的,所以它们的请求是重叠的,那么计算它们的 RTT 时,只需要计算体积最大的那个数据就可以了。
系统性地考虑优化方案了。
总的优化原则就是减少关键资源个数,降低关键资源大小,降低关键资源的 RTT 次数。

  • 如何减少关键资源的个数? js css 内联 js 没有代码操作 改成 async defer。css 媒体查询
  • 如何减少关键资源的大小 压缩 css js 移除 html css js 注释内容(async defer css 媒体查询)
  • 如何减少关键资源的RTT次数? 可以通过减少关键资源的个数和减少关键资源的大小搭配来实现。除此之外,还可以使用 CDN 来减少每次 RTT 时长。

    交互阶段

    在交互阶段,帧的渲染速度决定了交互的流畅度。
    image.png大部分情况下,生成一个新的帧都是由 JavaScript 通过修改 DOM 或者 CSSOM 来触发的。
    还有另外一部分帧是由 CSS 来触发的。
    重排 重绘
    通过 CSS 实现一些变形、渐变、动画等特效,这是由 CSS 触发的,并且是在合成线程上执行的,这个过程称为合成。因为它不会触发重排或者重绘,而且合成操作本身的速度就非常快,所以执行合成是效率最高的方式
    优化方案的原则:
    一个大的原则就是让单个帧的生成的速度变快。

  • 减少 JavaScript 脚本执行时间 函数分解多个任务 Web Workers

  • 避免强制同步布局
    1. - 布局操作 通过 DOM 接口执行添加元素或者删除元素等操作后,是需要重新计算样式和布局的,不过正常情况下这些操作都是在另外的任务中异步完成的,这样做是为了避免当前的任务占用太长的主线程时间
    ```

  • time
  • geekbang
    1. <p id="demo">强制布局demo</p>
    2. <button onclick="foo()">添加新元素</button>
    3. <script>
    4. function foo() {
    5. let main_div = document.getElementById("mian_div")
    6. let new_node = document.createElement("li")
    7. let textnode = document.createTextNode("time.geekbang")
    8. new_node.appendChild(textnode);
    9. document.getElementById("mian_div").appendChild(new_node);
    10. }
    11. </script>

    1. - 所谓强制同步布局,是指 JavaScript 强制将计算样式和布局操作提前到当前的任务中。
    2. - 为了避免强制同步布局,我们可以调整策略,在修改 DOM 之前查询相关值

    function foo() { let main_div = document.getElementById(“mian_div”) let new_node = document.createElement(“li”) let textnode = document.createTextNode(“time.geekbang”) new_node.appendChild(textnode); document.getElementById(“mian_div”).appendChild(new_node); //由于要获取到offsetHeight, //但是此时的offsetHeight还是老的数据, //所以需要立即执行布局操作 console.log(main_div.offsetHeight) }

    1. 将新的元素添加到 DOM 之后,我们又调用了main_div.offsetHeight来获取新 main_div 的高度信息。如果要获取到 main_div 的高度,就需要重新布局,所以这里在获取到 main_div 的高度之前,JavaScript 还需要强制让渲染引擎默认执行一次布局操作。我们把这个操作称为**强制同步布局。**
    2. - **避免抖动 **所谓布局抖动,是指在一次 JavaScript 执行过程中,多次执行强制布局和抖动操作。

    function foo() { let time_li = document.getElementById(“time_li”) for (let i = 0; i < 100; i++) { let main_div = document.getElementById(“mian_div”) let new_node = document.createElement(“li”) let textnode = document.createTextNode(“time.geekbang”) new_node.appendChild(textnode); new_node.offsetHeight = time_li.offsetHeight; document.getElementById(“mian_div”).appendChild(new_node); } } ``` 我们在一个 for 循环语句里面不断读取属性值,每次读取属性值之前都要进行计算样式和布局
    这种情况的避免方式和强制同步布局一样,都是尽量不要在修改 DOM 结构时再去查询一些相关值

    • 合理利用CSS合成动画 wil-change
    • 避免频繁的垃圾回收 JavaScript 使用了自动垃圾回收机制,如果在一些函数中频繁创建临时对象,那么垃圾回收器也会频繁地去执行垃圾回收策略。这样当垃圾回收操作发生时,就会占用主线程,从而影响到其他任务的执行,严重的话还会让用户产生掉帧、不流畅的感觉。所以要尽量避免产生那些临时垃圾数据。那该怎么做呢?可以尽可能优化储存结构,尽可能避免小颗粒对象的产生。

      总结

    • 在加载阶段,核心的优化原则是:优化关键资源的加载速度,减少关键资源的个数,降低关键资源的 RTT 次数。

    • 在交互阶段,核心的优化原则是:尽量减少一帧的生成时间。可以通过减少单次 JavaScript 的执行时间、避免强制同步布局、避免布局抖动、尽量采用 CSS 的合成动画、避免频繁的垃圾回收等方式来减少一帧生成的时长。