前端路 - JavaScript 性能优化
浏览器内存管理
基本概念
- 内存
- 由可读写的单元组成,表示一片可操作空间
- JS 中的内存空间在变量定义时自动分配,无法人为指定明确大小
- 管理:认为的去操控一片空间的申请、使用和释放
- 内存管理:开发者主动申请空间、使用空间、释放空间
- 管理流程:申请 => 使用 => 释放
JavaScript 中垃圾的定义
- 不再被引用的对象
- 不能从根*问到的对象(非可达对象)
- 程序中不再需要使用的对象
- 程序中不能再访问到的对象
JavaScript 中可达对象的定义
- 可以访问到的对象
- 通过根可以有路径(如引用、作用域链)查找到的对象
内存问题
- 外在表现
- 页面出现延迟加载或者经常性暂停(网络正常情况)
- 因为某部分程序使得内存急剧升高,导致底层存在频繁的垃圾回收
- 页面持续性出现糟糕的性能
- 程序运行时,为了是当前页面实现最大运行速度,所需要(申请)的内存远超过了设备本身所能提供的大小,即内存膨胀
- 页面的性能随时间延长越来越差
- 存在内存泄露
- 程序运行时,未能及时清理导致内存持续升高,最终内存泄漏
- 页面出现延迟加载或者经常性暂停(网络正常情况)
- 监控方式
- 浏览器任务管理器
- Timline 时序图记录
- 堆快照查找分离 DOM
- 判断是否存在频繁的垃圾回收
- GC 工作时,应用程序时停止的
- 频繁且过长的 GC 会导致应用假死
- 用户使用感知应用卡顿
GC 算法
基本概念
- GC是一种垃圾回收的工作机制
- 工作的内容就是查找内存中的垃圾并释放和回收空间
- 算法,即工作时查找和回收所遵循的规则
常用 GC 算法
引用计数 | 标记清除 | 标记整理 | |
---|---|---|---|
核心思想 | 就是设置引用数,判断当前对象的引用数是否为 0 | 分标记和清除两个阶段 | 可以看做是标记清除的增强 |
实现原理 |
1. 当前对象的引用关系改变时,修改其引用数字 2. 当引用数字为 0 时,立即回收该对象 |
1. 遍历所有对象查找标记活动对象 2. 遍历所有对象清除没有标记的对象 3. 回收相应的空间 |
1. 标记阶段的操作和标记清除一致 2. 清除阶段会先执行整理,移动对象的位置,使得回收的空间连续 |
优点 |
可以及即时回收垃圾对象 最大限度减少程序卡顿(保证空间不会被占满) |
可以解决循环引用不能回收的问题 | |
缺点 |
无法回收循环引用的对象 时间资源开销大(监控对象,维护引用数) |
因为标记的对象空间不完全连续,回收后有可能产生大量碎片化空间,浪费空间,不利于内存使用的最大化 |
V8 引擎
概述
V8 引擎是一款主流的 JavaScript 执行引擎,采用的是即时编译的形式。V8 引擎本身是为浏览器而生的引擎,过大的内存占用会影响浏览器的性能,因此,V8 引擎的内存设有上限。而且,V8 引擎将内存空间一分为二,其中,较小的内存空间用于存储新生代对象,大空间用于存储老年代对象。
垃圾回收策略
V8 引擎采用的是分代回收思想,将内存分为新生代和老年代,针对不同的对象采用不同的回收算法。
新生代(存活时间较短的对象)的回收实现
- 回收过程采用复制算法 + 标记整理
- 新生代内存区分为 2 个等大小的空间
- 使用空间为 From,空闲空间为 To
- 活动对象存储于 From 空间
- 标记整理后将活动对象拷贝至 To
- From 与 To 交换空间完成释放
- * 拷贝过程中,若一轮 GC 后,某个新生代对象仍然存留,则该新生代的对象会被移动至老年代保存
老年代(存活时间较长的对象)的回收实现
- 使用标记清除完成空间垃圾的回收
- 采用标记整理进行空间优化
- 采用增量标记进行效率优化
性能优化方案
1、使用变量对 DOM 元素进行缓存
let oDiv = document.querySelector('#div')
通过使用变量来缓存 DOM 元素,在后续的使用中,直接调用变量即可操作对应的 DOM 元素,而不需要重新检索获取该 DOM 元素。从而,减少 DOM 元素的检索操作,优化性能。
2、通过原型对象添加附加方法
let fn1 = function() {
this.foo = function() {
console.log(11111)
}
}
let f1 = new fn1()
// ===>
let fn2 = function() {}
fn2.prototype.foo = function() {
console.log(11111)
}
let f2 = new fn2()
3、避开闭包陷阱
当闭包使用不当时,容易出现内存泄露,所以当闭包中的变量完成使用后,应置空
4、避免属性访问方法的使用
即,给对象添加方法,并通过该方法对对象属性做修改。
let obj = {
name: 'Azer',
getName() {
return this.name
}
}
因为对象所有的属性都是外部可见的,所以JS 是不需要属性访问方法的。使用属性访问方法只会增加一层重定义,没有访问的控制力
5、for 循环的优化
因为对于类数组元素的长度,每次引用都会触发新的计算,所以在 for 循环中,提前获取并缓存数组的长的,可以有效的减少重新计算的次数,提升性能。
for(var i = 0; i < arr.length; i++) {}
// ===>
for(var i = 0, len = arr.length; i < len; i++) {}
6、直接量替换 new 操作
let arr = new Array(3)
arr[0] = 1; arr[1] = 2; arr[2] = 3;
// ===>
let arr = [1, 2, 3]
上一篇: 关于构造函数内调用虚函数的问题
下一篇: Python-----反射