性能优化问题面试指南
从输入 URL 到页面加载显示完成都发生了什么
- URL 解析
- 检查缓存
- DNS 解析
- TCP 三次握手
- 客户端与服务端进行信息通信
- TCP 四次挥手
- 客户端渲染
URL解析: UI 线程会判断输入的地址地址是搜索的关键词还是访问站点的 URL
DNS 解析: 接下来 UI 线程会通知 Network 线程,让其发起网络请求
先要进行 DNS 查找,要去把域名转换成 IP 地址
建立链接过程中还要看协议,如果使 HTTPS 需要建立 TLS 连接
如果碰到 301 (永久移动)则要重新发起请求
Server 上处理请求,最后会组织成 Response 返回给前端
读到 Response 前几个字节会开始分析数据的类型,虽然我们会在
Content-Type
告诉服务器这个类型是什么,但是这个不一定是正确的,浏览器会对此进行判断(安全检查)
客户端渲染: 数据和渲染进程都准备好了会传递给主线程
主线程开始进行文本解析,把文档转换成 DOM 对象
主线程在解析构造 DOM 时,为了加快速度,会进行一个 预扫描 ,先把 HTML 里的标签给扫描出来。HTML 解析器碰到 JS 会暂停文档进一步解析(因为 JS 会修改 DOM) ,可以使用
async/defer
防止 JS 阻塞 HTML 解析主线程解析 CSS 计算样式,创建布局树 并确定每一个元素的几何位置
接下来任务交给绘画线程和复合线程来做
主线程遍历布局树后会创建绘制记录,因为绘画是有顺序的,之后将页面拆分构建成 图层树,最后复合线程把图层创建一成一个 复合帧
首屏加载优化
- Web 增量加载的特点决定了首屏性能不会完美
- 过长的白屏影响用户体验和留存
- 首屏(above the fold) -> 初次印象
首屏 —— 用户加载体验的 3 个关键时刻
测量指标:
- 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 都是近似实现,只能通过判断变量是否还能再次访问到
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 进行引用)
当创建变量后,去看一下有哪些对其进行引用,一旦被引用就不能被垃圾回收
标记清除
会从根节点进行扫描,去看一下所有根节点是否能被访问到,如果有节点不能被访问到就会被回收掉
js// b 属性始终没有用到,但是从根节点始终可以访问到 b 属性,所以 b 属性不能被回收 const object = { a: new Array(1000), b: new Array(2000) } setInterval(() => console.log(object.a), 1000)
解决方法:
避免意外的全局变量产生
jsfunction accidentalGlobal() { leak1 = 'leak1' this.leak2 = 'leak2' } accidentalGlobal() window.leak1 window.leak2
避免反复运行引用大量闭包
js// 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 元素
jsfunction 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()