1. javascript性能优化
1.1. 内容概要
- 内存管理
- 垃圾回收与常见GC算法
- V8引擎的垃圾回收
- Performance工具
1.2. 内存管理
申请---使用---释放,ecmascript不像c++那样可以主动调用api完成内存管理
1.3. 垃圾回收
什么是可达对象
- 可以访问到的对象(引用,作用域链)
- 可达的标准就是从根出发沿着作用域链能否被找到
- javascript中的根可以理解为全局变量对象
let obj = { name: 'zs' }
let p = obj
obj = null
// p是可达的
1.4. GC算法
GC是垃圾回收机制的简写,它可以找到内存中的垃圾,并释放和回收空间
GC中的垃圾是什么?
- 程序中不再需要使用的对象
- 程序中不能访问到的对象
1.4.1. 常见的GC算法
1.4.2. 引用计数
设置引用数,判断当前引用数是否为0;引用发生改变时修改引用数字,引用为0时立即回收
优点:
- 发现垃圾时立即回收
- 最大限度减少程序暂停
缺点
- 无法回收循环引用的对象
- 时间,资源开销大
标记清除法
将整个垃圾回收机制分成两个阶段,标记和清除
遍历所有对象找到所有活动对象(可达对象)进行标记
遍历所有对象清除没有标记的对象
回收相应空间
优点
解决循环引用不能回收的问题
缺点
由于地址不连续导致空间碎片化 不会立即回收垃圾对象
标记整理
标记清除的增强操作
标记阶段的操作和标记清除一致
清除阶段会先执行整理,移动对象位置
优点
减少碎片化空间
缺点
不会立即回收垃圾对象
1.5. V8垃圾回收策略
- 采用分代回收的思想,将内存分为新生代(存活时间较短的对象),老生代
- 针对不同对象采用不同算法
V8内存分配
- V8空间一分为二
- 小空间用于存储新生代对象
新生代对象回收实现 (空间换时间)
- 回收过程采用复制算法+标记整理
- 新生代内存分为两个等大空间
- 使用空间为From,空闲空间为To
- 标记整理后将活动对象拷贝至To
- From和To交换空间完成释放
老生代对象回收实现
- 主要采用标记清除,标记整理(新生代对象往老生代对象移动,而老生代空间不足时),增量标记算法
- 首先使用标记清除完成垃圾空间的回收
- 采用标记整理进行空间优化
- 采用增量标记进行效率优化
回收中可能出现晋升
1.6. DOM节点的类型
分为垃圾对象和分离DOM
- 垃圾对象: 在dom树上脱离,且js中也没有引用
- 分离DOM: 在dom树上脱离,但是js中有引用,此时会造成内存泄漏
chrome开发工具中的堆快照可以查到到分离dom (Detached)
1.7. 频繁gc
- GC工作时应用程序是停止的
- 频繁且过长的GC会导致应用假死
- 用户会感知到卡顿
如何确定
- timeline中频繁的上升下级
- 任务管理器中数据频繁的增加减小
1.8. 代码优化部分
1.8.1. 如何精准测试JavaScript性能
- 采集大量的执行样本进行数学统计和分析
- 使用基于benchmark.js的jsperf.com完成
1.8.2. 慎用全局变量
- 影响GC的垃圾清除
- 局部变量会遮蔽或污染全局变量
如通过document获取元素时,可以使用变量缓存document
1.8.3. 在原型对象添加附加方法
1.8.4. 避开闭包陷阱
- 外部具有指向内部的引用
- "外"部作用域访问"内"部作用域的数据
function foo() {
var el = document.getElementById('btn') // el本身是dom节点上的,此处存了下而已
el.onClick = function() { // 1. el引用了foo函数内部的function
console.log(el.id) // 用到了foo作用域的el
}
// 释放引用,因为GC不会回收这里的el引用
el = null
}
foo() // 2. 外部作用域访问到了foo内部的el
1.8.5. 避免属性访问方法使用
- js不需要属性的访问方法,所有属性都是外部可见的
- 使用属性访问方法只会增加一层重定义,没有访问的控制力
function Person () {
this.name = 'zs'
this.getAge = function () { // 性能略差
return this.age
}
}
1.8.6. 遍历优化
for循环优化,缓存数组长度
性能顺序 forEach > for > for...in
1.8.7. 节点优化
节点的操作伴随着回流和重绘
使用文档碎片优化节点添加
for(var i =0;i < 10;i++) {
var op = document.createElement('p')
document.body.appendChild(op)
}
// vs
const fragEle = document.createDocumentFragment()
for(var i = 0;i < 10;i++) {
var op = document.createElement('p')
fragEle.appendChild(op)
}
document.body.appendChild(fragEle)
1.8.8. 定义数组,对象时,使用字面量而不是构造式
1.9. 减少判断层级
无用的条件提前return调 明确的条件而不是区间使用switch...case代替if-else-if
1.10. 减少作用域链查找层级
每当一个函数执行的时候就会产生一个执行上下文,当函数执行完之后,如果没有闭包存在,上下文就会被销毁掉
1.11. 减少数据读取
访问字面量,局部变量是最快的,因为存在栈中
要减少时间查询消耗,就应该减少对象成员查找次数,和属性的嵌套层级,我们可以提前对对象进行缓存