本文为拉勾网大前端高薪训练营第一期笔记
Javascript中的垃圾
- 对象不再被引用时
- 对象不能从根上访问到时(不是可达对象)
常见的GC算法
- 引用计数
设置引用数,判断当前引用数是否为0
优点
- 发现垃圾时立即回收
- 最大限度减少程序卡顿,因为回收比较及时
缺点
-
无法回收循环引用的对象
-
资源开销大
-
标记清除
分标记和清除两个阶段完成
1.遍历所有对象找标记活动对象
2.遍历所有对象清除没有标记对象
3.回收相应的空间
优点:解决了引用计数的函数里的循环引用不可回收的问题
缺点:
-
回收后地址不连续,垃圾碎片化
-
不能立即回收垃圾
-
标记整理
标记清除的增强
1.标记阶段和上一算法一致
2.清除阶段会先执行整理,移动对象位置,活跃对象放在连续位置,后面空闲空间也是连续的
优点:减少碎片化空间
缺点:不能立即回收垃圾
- 分代回收
V8引擎
主流的JavaScript执行引擎
即时编译
内存设限 32位800M 64位1.5G,一方面是因为给浏览器使用,这个数值够用,另一方面是因为再大的话垃圾回收时间较长
内存回收算法是分代回收,内存分为新生代和老生代,不同的生代采用不同的回收算法
小空间用于存储新生代对象 64位32M 32位16M
新生代指的是存活时间较短的对象
V8中常见GC算法
- 分代回收:新生代老生代分开不同算法处理
- 空间复制
- 标记清除
- 标记整理
- 标记增量
新生代对象回收实现
- 采用复制算法+标记整理
- 新生代内存区分为两个等大小空间
- 使用空间为From 空闲空间为To
- 活动对象存储于From空间
- 标记整理后将活动对象拷贝至To
- From与To交换空间完成释放
回收细节说明
- 拷贝过程中可能出现晋升
- 晋升就是将新生代对象移动至老生代
- 一轮GC还存活的新生代需要晋升
- To空间的使用率超过25%时晋升
老生代对象回收
- 老生代对象存放在右侧老生代区域
- 64位操作系统1.4G 32位操作系统700M
- 老生代指的是存活时间较长的对象
实现
- 主要采用标记清除、标记整理、增量标记算法
- 首先使用标记清除完成垃圾空间的回收
- 有新生代往老生代移动,如果空间不足,就采用标记整理进行空间优化
- 采用增量标记进行效率优化
细节对比
- 新生代区域垃圾回收使用空间换时间
- 老生代区域垃圾回收不适合复制算法
增量标记就是把标记过程拆分,执行程序和标记交替进行,防止程序长时间卡顿等待垃圾标记和回收
为什么使用Performance
- GC的目的是为了实现内存空间的良性循环
- 良性循环的基石是合理使用
- 时刻关注才能确定是否合理
- Performance提供多种监控方式
内存问题的外在表现
- 页面出现延迟加载或经常性暂停
- 页面持续性出现糟糕的性能
- 页面的性能随时间延长越来越差
内存问题
内存泄露:内存使用持续升高
内存膨胀:在多数设备上都存在性能问题,由于设备不支持
频繁垃圾回收:通过内存变化图进行分析
监控内存的几种方式
- 浏览器任务管理器
- Timeline时序图记录
- 对快照查找分离DOM
- 判断是否存在频繁的垃圾回收
用chrome自带的任务管理器查看JS使用内存,第一列内存是dom占用的内存,js内存括号里是可达对象占用的内存
chrome> memory> heap snapshot可以搜detached查看分离的dom
如何判断频繁的垃圾回收
- Timeline中频繁的上升下降
- 任务管理器中数据频繁的增加减少
使用基于Benchmark.js的https://jsperf.com精准测试Javascript性能
本质上是采集大量的执行样本进行数学统计和分析
慎用全局变量
- 全局变量定义在全局执行上下文,在作用域链的顶端,查找起来时间消耗大
- 全局执行上下文一直存在于上下文执行栈,直到程序退出
- 局部作用域出现了同名变量,会遮蔽或污染全局
缓存全局变量性能更优
在局部作用域缓存全局变量(比如在局部作用域里let obj = document)能提升性能,不缓存慢0.73%
prototype的性能更优
var fn1 = function(){
this.foo = function(){
console.log('do something')
}
}
f1 = new fn1()
var fn2 = function(){}
fn2.prototype.foo = function() {
console.log('do something')
}
f2 = new fn2()
通过构造函数,不如通过原型对象加方法性能高,前者慢14%
闭包使用不当很容易出现内存泄露
事先定义函数性能更优
匿名函数,不如事先定义好函数来引用性能高,前者慢4%
直接访问类的属性性能更优
避免属性访问方法的使用,getAge()这种面向对象的写法性能差很多,前者慢28%
for从length递减到0性能更优
for循环优化,从0累加到arr.length,不如arr.length递减到0性能好,前者慢2%
遍历数组的方式 forEach性能更优
var arrList = new Array(1,2,3,4,5)
arrList.forEach(function(item){
console.log(item)
})
for(var i = arrList.length; i; i--){
console.log(arrList[i])
}
for(var i in arrList){
console.log(arrList[i])
}
第一种最快,第二种慢39%,第三种慢41%
forEach最快,for和forin差不多慢
添加DOM,用documentFragment性能更优
for(var i = 0; i < 10; i++){
var oP = document.createElement('p')
oP.innerHTML = i
document.body.appendChild(oP)
}
const fragEle = document.createDocumentFragment()
for(var i = 0; i < 10; i++){
var oP = document.createElement('p')
oP.innerHTML = i
fragEle.appendChild(oP)
}
document.body.appendChild(fragEle)
前者慢30%
通过克隆节点来添加的性能更优
<p id="box1">old</p>
<script>
for(var i = 0; i < 3; i++){
var oP = document.createElement('p')
oP.innerHTML = i
document.body.appendChild(oP)
}
var oldP = document.getElementById('box1')
for(var i = 0; i < 3; i++){
var newP = oldP.cloneNode(false)
newP.innerHTML = i
document.body.appendChild(oP)
}
</script>
前者慢8%
直接量比new Object性能更优
var a1 = new Array(3)
a1[0] = 1
a1[1] = 2
a1[2] = 3
var a = [1, 2, 3]
前者慢2%
本文为拉勾网大前端高薪训练营第一期笔记