写在前面
笔记内容大多出自于拉勾教育大前端高薪训练营的教程,因此也许会和其他文章雷同较多,请担待。
JS内存管理
- 内存 -> 由可读写单元组成,表示一片可操作空间
- 管理 -> 人为地去操作一片空间的申请、使用、释放
- 内存管理 -> 开发者主动申请空间、使用空间、释放空间
// 申请内存空间
let obj = {}
// 使用内存空间
obj.name = 'aeorus'
// 释放内存空间
obj = null
引用计数算法
- 设置引用数,判断当前引用数是否为0
- 引用关系改变时修改引用数字
- 引用数字为0时,立马回收垃圾
- 优点 -> 发现垃圾时立即回收 / 最大限度减少程序暂停
- 缺点 -> 无法回收循环引用的对象 / 时间开销大(需要时刻监控,而且当修改的值比较多时,那需要的时间也更长)
标记清除算法
- 分标记和清除两个阶段完成
- 遍历所有对象寻找活动对象进行标记(可达对象)
- 遍历所有对象清除没有标记的对象(抹掉第一次遍历打上的标记)
- 回收相应的空间
- 优点 -> 可以解决引用计数算法循环引用的对象不能被回收的问题
- 缺点 -> 空间碎片化
标记整理算法
- 可以看做标记清除算法的增强
- 标记阶段的操作和标记清楚算法一致
- 清除阶段会先执行整理,移动对象位置,让它们在空间链表上的位置是连续的,不会产生碎片化问题
V8
- 主流的JavaScript执行引擎
- 采用即时编译
- V8内存设限 -> 64位1.5G 32位800M
V8垃圾回收策略
- 分代回收思想
- 内存分为新生代、老生代
- 针对不同对象采用不同算法
- 新生代对象储存 -> 采用具体的GC算法
- 老生代对象储存 -> 采用具体的算法
- 分代回收 / 空间复制 / 标记清除 / 标记整理 / 标记增量
因为V8的内存是有上限的,所以要采用分代回收思想,不同的对象采用更加适合的算法,以此达到更加高效的垃圾回收机制
新生代对象 -> 存活时间较短的对象(例如函数内定义的变量)
- 内存64位32M 32位16M
- 使用空间From,空闲空间To
- 活动对象存储于From空间
- 标记整理后将活动对象拷贝至To
- 释放From
- From和To交换空间
老生代对象 -> 存活时间较长的对象(闭包或者全局定义的变量)
- 内存64位1.4G 32位700M
- 主要采用标记清除 / 标记整理 / 增量标记算法
- 首先使用标记清除完成垃圾空间的回收
- 采用标记整理进行空间优化(新生代晋升老生代时)
- 采用增量标记进行效率优化
内存问题的表现
- 页面出现延迟加载或经常性暂停
- 持续性出现糟糕的性能(内存膨胀)
- 页面的性能随时间延长越来越差(内存泄漏)
监控内存
内存泄漏 -> 内存使用持续升高
内存膨胀 -> 在多数设备上都存在性能问题
频繁垃圾回收 -> 通过内存变化图进行分析
- 浏览器任务管理器
- Timeline时序图记录 -> Performance
- 堆快照查找分离DOM -> Memory
- 判断是否存在频繁的垃圾回收
V8引擎工作流程
渲染引擎 ---(utf-8 chunks)--->
Stream ---(utf-16 code utils)--->
Scanner ---(tokens)--->
(PreParser) -> Parser ---(AST)--->
lgnition ---(bytecode)--->
TurboFan
防抖和节流
防抖 -> 持续触发事件,不停止触发就不执行
- 使用setTimeout定时器,一直调用fn,一直清除定时器直到清除完后不再调用fn时才能真正激活定时器内的fn
- 因为从调用fn变成了调用debounce,所以this的指向也会改变,所以需要使用bind改回最初的this
function debounce(fn, delay) {
let timer = null
return function() {
if (!timer) {
clearTimeout(timer)
} else {
timer = setTimeout(fn.bind(this, ...arguments), delay)
}
}
}
节流 -> 持续触发事件,每隔一段时间,只执行一次事件
- 使用时间戳,当触发事件的时候,我们取出当前的时间戳,然后减去之前的时间戳(最一开始值设为 0 )
- 如果大于设置的时间周期,就执行函数,然后更新时间戳为当前的时间戳,如果小于,就不执行
function throttle(fn, delay) {
let timer = null
return function() {
if (timer !== null) return
timer = setTimeout(() => {
timer = null
fn.call(this, ...arguments)
})
}
}
function throttle(fn, delay) {
let prev = 0
return function() {
const now = +new Date()
if (now - prev >= delay) {
prev = now
fn.call(this, ...arguments)
}
}
}