前端性能优化常见问题(访问URL流程、首屏加载、JS内存管理)

目录

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

二:首屏加载优化

三:JavaScript 内存管理


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

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

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

  1. 先要进行 DNS 查找,要去把域名转换成 IP 地址
  2. 建立链接过程中还要看协议,如果使 HTTPS 需要建立 TLS 连接
  3. 如果碰到 301 (永久性移走)则要重新发起请求
  4. Server 上处理请求,最后会组织成 Response 返回给前端
  5. 读到 Response 前几个字节会开始分析数据的类型,虽然我们会在 header 部分 告诉服务器Content-Type 是什么,但是这个不一定是正确的,浏览器会对此进行判断(安全检查

 前面介绍的 UI 线程 和 Network 线程 其实都属于浏览器进程 ,接下来渲染的工作会交给渲染进程去做

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

  1. 主线程在解析构造 DOM 时,为了加快速度,会进行一个 预扫描 ,先把 HTML 里的标签给扫描出来。HTML 解析器碰到 JS 会暂停文档进一步解析(因为 JS 会修改 DOM) ,可以使用 async/defer 防止 JS 阻塞 HTML 解析
  2. 主线程解析 CSS 计算样式,创建布局树并确定每一个元素的大小&位置
  3. 接下来任务交给绘画线程复合线程来做。主线程遍历布局树后会创建绘制记录,因为绘画是有顺序的,之后将页面拆分构建成 图层树,最后复合线程把图层创建一成一个 复合帧
     

二:首屏加载优化

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

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

 测量指标:

 解决方案:

资源体积太大

  • 对 HTML、CSS、JS(压缩与混淆) 进行资源压缩
  • 传输压缩启用 Gzip
  • 使用 splitChunks 进行代码拆分(将第三方库、业务逻辑、公共代码拆分出去)
  • 使用 TreeShaking 将没有用到的代码摇下去(基于 ES6 import export)
  • 利用 HTTP 2 多路复用加快资源加载
  • 合理利用缓存(Cache-Control/Expires)

首页内容太多

  • 路由、组件、图片列表进行懒加载
  • 使用预渲染或 SSR,把 HTML 内容进行提前生成减少请求传输时间
  • 把首屏最需要的 CSS 嵌入到页面 Inline CSS

加载顺序不合适

  • 使用 prefetch、preload 提高加载顺序
     

三:JavaScript 内存管理

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

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

  • 内存释放的主要问题是如何确定不再需要使用的内存
  • 所有的 GC(垃圾回收) 都是近似实现,只能通过判断变量是否还能再次访问到
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(Garbage Collection) 实现机制

  • 引用计数 —— 无法解决循环引用的问题(a 引用 b,b 引用 a,即使其他变量没有对 a 和 b 进行引用)当创建变量后,去看一下有哪些对其进行引用,一旦被引用就不能被垃圾回收
  • 标记清除 —— 会从根节点进行扫描,去看一下所有根节点是否能被访问到,如果有节点不能被访问到就会被回收掉

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

解决方法:

  • 避免意外的全局变量产生
function accidentalGlobal() {
  leak1 = 'leak1'
  this.leak2 = 'leak2'
}
accidentalGlobal()
window.leak1
window.leak2
  • 避免反复运行引用大量闭包
// 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 元素
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()

猜你喜欢

转载自blog.csdn.net/qq_42691298/article/details/128574236