目录
一:从输入 URL 到页面加载显示完成都发生了什么
二:首屏加载优化
三:JavaScript 内存管理
一:从输入 URL 到页面加载显示完成都发生了什么
UI 线程会判断输入的地址地址是搜索的关键词还是访问站点的 URL
接下来 UI 线程会通知 Network 线程,让其发起网络请求
- 先要进行 DNS 查找,要去把域名转换成 IP 地址
- 建立链接过程中还要看协议,如果使 HTTPS 需要建立 TLS 连接
- 如果碰到 301 (永久性移走)则要重新发起请求
- Server 上处理请求,最后会组织成 Response 返回给前端
- 读到 Response 前几个字节会开始分析数据的类型,虽然我们会在 header 部分 告诉服务器Content-Type 是什么,但是这个不一定是正确的,浏览器会对此进行判断(安全检查)
前面介绍的 UI 线程 和 Network 线程 其实都属于浏览器进程 ,接下来渲染的工作会交给渲染进程去做
主线程开始进行文本解析,把文档转换成 DOM 对象
- 主线程在解析构造 DOM 时,为了加快速度,会进行一个 预扫描 ,先把 HTML 里的标签给扫描出来。HTML 解析器碰到 JS 会暂停文档进一步解析(因为 JS 会修改 DOM) ,可以使用 async/defer 防止 JS 阻塞 HTML 解析
- 主线程解析 CSS 计算样式,创建布局树并确定每一个元素的大小&位置
- 接下来任务交给绘画线程和复合线程来做。主线程遍历布局树后会创建绘制记录,因为绘画是有顺序的,之后将页面拆分构建成 图层树,最后复合线程把图层创建一成一个 复合帧
二:首屏加载优化
- 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()