从输入 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 都是近似实现,只能通过判断变量是否还能再次访问到
1 | var number = 123 // 给数分配内存 |
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
7function 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
12function 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()