5.JavaScript性能优化笔记

本文探讨了JavaScript中的垃圾回收机制,包括引用计数、标记清除、标记整理和分代回收等常见算法,并深入解析V8引擎的内存管理。文章强调了全局变量的慎用,提倡局部作用域缓存、原型链优化、预定义函数等提高性能的方法。同时,通过对比不同的DOM操作和遍历方式,提出了一些优化建议,如使用documentFragment、克隆节点以及直接量代替new Object。
摘要由CSDN通过智能技术生成

本文为拉勾网大前端高薪训练营第一期笔记


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%


本文为拉勾网大前端高薪训练营第一期笔记

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值