漫漫前端路之浏览器基础——页面系统优化篇

137 阅读3分钟

页面的三个阶段

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

加载阶段

image.png 在页面首次渲染中,并不是所有资源都会产生影响,比如非关键资源,音频,视频,图片等,而关键资源html、css、js文件是会产生影响的,因此基于这个事实可以得出

影响页面首次渲染的核心因素

  1. 关键资源个数
  2. 关键资源大小
  3. 请求关键资源消耗的RTT个数:RTT往返时延指的是从发送端发送数据后,发送端收到来自接收端的确认所经历的时延。通常情况下,1个http数据包在14kb,那么一个0.1MB的页面大概需要8个RTT。在上图中,预解析后下载Js、css文件的时间是重叠的,所以算最长的即可。因此,上图消耗2个RTT。

优化首次渲染速度

  1. js、css改成内联的形式,若js没有操作DOM或者CSSDOM的话,可加上defer/async,谋体查询,当 JavaScript 标签加上了 async 或者 defer、CSSlink 属性之前加上了取消阻止显现的标志后,它们就变成了非关键资源了
  2. 压缩文件,删除注释
  3. 利用CDN加速

交互阶段

因为在交互阶段,帧的渲染速度决定了交互的流畅度。因此讨论页面优化实际上就是讨论渲染引擎是如何渲染帧的,否则就无法优化帧率。和加载阶段的渲染流水线有一些不同的地方是,在交互阶段没有了加载关键资源和构建 DOM、CSSOM 流程,通常是由 JavaScript 触发交互动画的 image.png

影响帧生成速度因素及优化方案

减少js脚本执行时间

有时脚本执行时间过长,会严重霸占主线程执行其他渲染任务的时间

  • 将任务拆成多个任务
  • 采用web workers, 其可以当作主线程之外的一个线程,在里面可执行脚本,但是在web workers中没有DOM、CSSOM环境,在其中访问不到DOM、CSSOM,因此,只适合放一些和DOM、CSSOM无关的操作。

避免强制同步布局

通常情况下,通过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)
}

所谓强制同步布局,是指 JavaScript 强制将计算样式和布局操作提前到当前的任务中

  • 为了避免强制同步布局,我们可以调整策略,在修改 DOM 之前查询相关值
function foo() {
    let main_div = document.getElementById("mian_div")
    //为了避免强制同步布局,在修改DOM之前查询相关值
    console.log(main_div.offsetHeight)
    let new_node = document.createElement("li")
    let textnode = document.createTextNode("time.geekbang")
    new_node.appendChild(textnode);
    document.getElementById("mian_div").appendChild(new_node);
    
}

避免布局抖动

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);
    }
}
  • 尽量不要在修改 DOM 结构时再去查询一些相关值

合理利用CSS合成动画

避免频繁的垃圾回收

我们知道 JavaScript 使用了自动垃圾回收机制,如果在一些函数中频繁创建临时对象,那么垃圾回收器也会频繁地去执行垃圾回收策略。这样当垃圾回收操作发生时,就会占用主线程,从而影响到其他任务的执行,严重的话还会让用户产生掉帧、不流畅的感觉。

  • 尽可能优化储存结构,尽可能避免小颗粒对象的产生

资料来源

time.geekbang.org/column/arti…