前言:代码为什么需要优化?
- web应用日益丰富,用户体验至关重要,前端性能备受关注
- 实现相同的结果下,哪种类型JavaScript代码具有更高性能
- 高性能的背后是数据快速存取,也是优秀内存管理的体现
JavaScript内存管理(Memory Management):
- 内存:有可读写的单元组成,表示一片可操作空间
- 管理:人为的去操作一片空间的申请、使用和释放
- 内存管理:开发者主要申请空间、使用空间、释放空间
- 管理流程:申请 - 使用 - 释放
JavaScript中的垃圾回收:
- JavaScript中内存管理是自动的
- 对象不再引用时被视为垃圾
- 对象不能从根上访问到时是垃圾
JavaScript中的可达对象:
- 可以访问到的对象就是可达对象
- 可达的标准就是从根出发是否能够被找到
- JavaScript中的根就可以理解为是全局变量对象
GC算法介绍:
- GC的定义与作用:GC垃圾回收机制的简写;GC可以找到内存中的垃圾、并释放和回收空间
- GC里的垃圾:程序中不再需要使用的对象,程序中不能再访问到的对象
- GC算法:
- GC是一种机制、垃圾回收器完成具体的工作;
- 工作的内容就是查找垃圾释放空间、回收空间
- 算法就是工作时查找和回收所遵循的规则
常见的GC算法:
引用计数:
-
核心思想:设置引用数,判断当前引用数是否为0,引用计数器,引用关系改变时修改引用数字,引用数字为0时立即回收;
-
优点:发现垃圾时立即回收,最大限度减少程序暂停
-
缺点:无法回收循环引用的对象;时间开销大;资源消耗大;
标记清除:
-
核心思想:分标记和清除两个阶段完成,遍历所有对象标记活动对象,遍历所有对象标记清除未标记对象,回收相应空间;
-
优点:可回收循环引用的对象
-
缺点:不能立即回收垃圾对象,空间碎片化(回收后的空间不连续)
标记整理
-
核心思想:标记整理可以看做是标记清除的增强,标记阶段的操作和标记清除一致,清除阶段会先执行整理,移动对象的位置,再进行清除并回收
-
优点:减少碎片化空间
-
缺点:不可立即回收
分代回收:内存分为新生代、老生代,针对不同对象采用不同算法,具体见V8垃圾回收机制
关于V8:
认识V8:
- 一款主流的JavaScript执行引擎
- V8采用即时编译(速度快)
- V8内存设限(64位为1.5G,32位为800M)
V8垃圾回收策略:
-
采用分代回收的思想:内存分为新生代、老生代,针对不同对象采用不同算法
-
V8中常用GC算法:分代回收、空间复制、标记清除、标记整理、标记增量
-
V8如何回收新生代对象:新生代指的是存活时间较短啊的对象;
- 内存分配:V8内存一分为二,小空间用于存储新生代对象(32M/16M);
- 新生代对象回收实现:复制算法+标记整理 => 新生代内存区分为两个等大的内存空间,使用空间From,空闲空间To => 活动对象存储于From空间 => 标记整理后将活动对象拷贝至To => From 与 To交换空间完成释放
- 回收细节说明:拷贝过程中可能出现晋升,就是将新生代对象移动至老生代,一轮GC还存活的新生代需要晋升,To空间的使用率超过25%
-
V8如何回收老生代对象:老生代指的是存活时间较旧的对象
- 老生代对象回收实现:主要采用标记清除、标记整理、增量标记算法;首先采用标记清除完成垃圾空间的回收,采用标记整理进行空间优化,采用增量标记进行效率优化;(标记增量:由于垃圾回收会阻塞js代码执行,所以回收机制会将任务拆分进行操作)
-
关于新生代对象与老生代对象回收细节的对比:
- 新生代区域垃圾回收使用空间换时间(本身空间小,时间提升大)
- 老生代区域垃圾回收不适合复制算法(老生代对象数据较大,空间较大)
Performance工具介绍:
为什么要是用Performance?
- GC的目的:实现内存空间的良性循环(空间的合理分配)
- Performance提供多种监控方式,时刻关注才能确定空间使用是否合理,则Performance可以时刻监控内存
内存问题的体现:
- 延迟加载/经常性暂停(频繁的垃圾回收)
- 持续性糟糕的性能(内存膨胀 => 申请内存空间大小超过内存本身提供大小)
- 性能随时间延长越来越差(内存泄漏)
界定内存问题的标准:
- 内存泄漏:内存使用持续升高(无下降节点)
- 内存膨胀:当前应用程序本身为达到最优效果,需要很大的内存空间,也许是硬件,程序or设备,在不同设备上运行测试
- 频繁的垃圾回收:通过内存变化图进行分析
监控内存的几种方式:
- 浏览器任务管理器
- Timeline时序图记录
- 堆快照查找分离DOM
- 判断是否存在频繁的垃圾回收
判断是否存在频繁GC:
- GC工作时应用程序是停止的
- 频繁且过长的GC会导致应用假死
- 用户使用中感知应用卡顿
代码优化具体操作:
- 慎用全局变量,全局变量持续占用内存,明确数据作用域的情况下,尽量用局部变量,减少全局查找时间消耗
- 通过原型新增方法:在原型对象上新增实例对象需要的方法
- 避开闭包陷阱:闭包使用不当很容易造成内存泄露
- 避免属性访问方法使用
- 采用最优循环方式:for循环优化(例1)
//例1:
var arrList = []
arrList[10000] = 'icoder'
for (var i = 0; i < arrList.length; i++) {
console.log(arrList[i])
}
// 用下述方法代替上面的,减少js运算过程
for (var i = arrList.length; i; i--) {
console.log(arrList[i])
}
//例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])
}
- 节点添加优化:将查找的固定节点提前定义,在循环中不必每次都去查找
// 优化前:
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)
- 克隆优化节点操作:通过clone不必每次创建DOM
// 优化前:
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(newP)
}
- 直接量替换Object操作:多用字面量定义
- 减少判断层级:
- 明确枚举值的情况下使用switch - case 判断
- 避免多层嵌套,固定的条件结果提前判断return
- 减少作用域链查找层级:在作用域链内部定义变量,多用const,let辅助,尽量不使用var
- 减少数据读取次数:缓存数据(会有内存消耗)
- 字面量与构造式:字面量性能高于构造式(引用类型差异无大)
- 减少声明与语句数:不是每次都用到的数据,不需要进行缓存,直接return
- 惰性函数与性能:使用惰性函数提升性能
- 采用事件委托:利用js事件冒泡机制,把原本需要绑定在子元素上的响应事件委托给父元素,父元素完成事件监听,减少事件子元素的事件注册。
结语:以上内容全学习时手敲记录,无复制粘贴,全原创,希望可以给各位小伙伴带来收获,如有错误的地方或有疑问欢迎留言,感谢阅读!
祝各位前端程序猿前程似锦,一路向北!
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!