0%

优化8.性能优化问题面试指南

从输入 URL 到页面加载显示完成都发生了什么

  1. URL 解析
  2. 检查缓存
  3. DNS 解析
  4. TCP 三次握手
  5. 客户端与服务端进行信息通信
  6. TCP 四次挥手
  7. 客户端渲染

URL解析:UI 线程会判断输入的地址地址是搜索的关键词还是访问站点的 URL

DNS 解析: 接下来 UI 线程会通知 Network 线程,让其发起网络请求

  • 先要进行 DNS 查找,要去把域名转换成 IP 地址

  • 建立链接过程中还要看协议,如果使 HTTPS 需要建立 TLS 连接

  • 如果碰到 301 (永久移动)则要重新发起请求

  • Server 上处理请求,最后会组织成 Response 返回给前端

    读到 Response 前几个字节会开始分析数据的类型,虽然我们会在 Content-Type 告诉服务器这个类型是什么,但是这个不一定是正确的,浏览器会对此进行判断(安全检查)

客户端渲染: 数据和渲染进程都准备好了会传递给主线程

主线程开始进行文本解析,把文档转换成 DOM 对象

  1. 主线程在解析构造 DOM 时,为了加快速度,会进行一个 预扫描 ,先把 HTML 里的标签给扫描出来。HTML 解析器碰到 JS 会暂停文档进一步解析(因为 JS 会修改 DOM) ,可以使用 async/defer 防止 JS 阻塞 HTML 解析

  2. 主线程解析 CSS 计算样式,创建布局树 并确定每一个元素的几何位置

  3. 接下来任务交给绘画线程和复合线程来做

    主线程遍历布局树后会创建绘制记录,因为绘画是有顺序的,之后将页面拆分构建成 图层树,最后复合线程把图层创建一成一个 复合帧

首屏加载优化

  • Web 增量加载的特点决定了首屏性能不会完美
  • 过长的白屏影响用户体验和留存
  • 首屏(above the fold) -> 初次印象

首屏 —— 用户加载体验的 3 个关键时刻

Web Page Usability Matters

测量指标:

  • First Contentful Paint(FCP)
  • Largest Contentful Paint(LCP)
  • Time to Interactive(TTI)

影响原因:

  • 资源体积太大

    对 HTML、CSS、JS(压缩与混淆) 进行资源压缩

    传输压缩启用 Gzip

    使用 splitChunks 对代码拆分(将第三方库、业务逻辑、公共代码拆分出去)

    使用 TreeShaking 将没有用到的代码摇下去(基于 ES6 import export)

    利用 HTTP 2 多路复用加快资源加载

    合理利用缓存(Cache-Control/Expires)

  • 首页内容太多

    路由、组件、图片列表进行懒加载

    使用预渲染或 SSR,把 HTML 内容进行生成减少请求传输时间

    把首屏最需要的 CSS 嵌入到页面 Inline CSS

  • 加载顺序不合适

    使用 prefetch、preload 提高加载顺序

JavaScript 内存管理

  • 内存泄露严重影响性能
  • 高级语言不等于不需要管理内存

变量创建时自动分配内存,不使用时 “自动” 释放内存(GC)

  • 内存释放的主要问题是如何确定不再需要使用的内存
  • 所有的 GC 都是近似实现,只能通过判断变量是否还能再次访问到
1
2
3
4
5
6
7
8
9
10
var number = 123 // 给数分配内存
var string = 'myString' // 给字符串分配内存
var obj = {
m: 1,
n: onj2,
} // 给对象和它的属性分配内存
var a = [1, null, 'str'] // 给数组和它包含的值分配内存
function f(a) {
return a + 3
} // 给函数分配内存

JavaScript 有相关作用域

  • 局部变量,函数执行完,没有闭包引用,就会被标记回收
  • 全局变量,直至浏览器卸载页面时释放

GC 实现机制

  • 引用计数 —— 无法解决循环引用的问题(a 引用 b,b 引用 a,即使其他变量没有对 a 和 b 进行引用)

    当创建变量后,去看一下有哪些对其进行引用,一旦被引用就不能被垃圾回收

  • 标记清除

    会从根节点进行扫描,去看一下所有根节点是否能被访问到,如果有节点不能被访问到就会被回收掉

    1
    2
    3
    // b 属性始终没有用到,但是从根节点始终可以访问到 b 属性,所以 b 属性不能被回收
    const object = { a: new Array(1000), b: new Array(2000) }
    setInterval(() => console.log(object.a), 1000)

解决方法:

  • 避免意外的全局变量产生

    1
    2
    3
    4
    5
    6
    7
    function accidentalGlobal() {
    leak1 = 'leak1'
    this.leak2 = 'leak2'
    }
    accidentalGlobal()
    window.leak1
    window.leak2
  • 避免反复运行引用大量闭包

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // store会持有outer的上下文
    var store
    function outer() {
    var largeData = new Array(1000)
    var preStore = store
    function inner() {
    if (preStore) return largeData
    }
    return function () {}
    }
    setInterval(function () {
    store = outer()
    }, 10)
  • 避免脱离的 DOM 元素

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    function createElement() {
    const div = document.createElement('div')
    div.id = 'detached'
    return div
    }
    const detachedDiv = createElement()
    document.body.appendChild(detachedDiv)
    function deleteElement() {
    document.body.removeChild(document.getElementById('detached'))
    }
    // 虽然 DOM 删除了,但是变量还在引用
    deleteElement()