JavaScript 性能优化
内容概要
1.内存管理
2.垃圾回收与常见GC算法
3.V8引擎的垃圾回收
4.Performance 工具
5.代码优化实例
JavaScript 内存管理
1.内存:由可读写单元组成,表示一片可操作空间
2.管理:人为的去操作一片空间的申请、使用和释放
3.内存管理:开发者主动申请空间、使用空间、释放空间
4.管理流程:申请-使用-释放
// 申请
let obj = {}
//使用
obj.name = 'lg'
//释放
obj = null
JavaScript 中的垃圾回收
1.JavaScript 中的内存管理是自动的
2.对象不再被引用时就是垃圾
3.对象不能从根上访问到时垃圾
JavaScript 中的可达对象
1.可以访问到的对象就是可达对象(引用、作用域链)
2.可达的标准就是从根出发是否能够被找到
3.JavaScript中的根就可以理解为是全局变量对象
GC定义与作用
1.GC就是垃圾回收机制的简写
2.GC可以找到内存中的垃圾、并释放和回收空间
(1)程序中不再需要使用的对象
function func(){
name = 'lg'
return `${name} is a coder`
}
func()
(2)程序中不能再访问到的对象`
function func(){
const name = 'lg'
return `${name} is a coder`
}
func()
3.GC是一种机制,垃圾回收器完成具体的工作
4.工作的内容就是查找垃圾释放空间、回收空间
5.算法就是工作时查找和回收所遵循的规则
6.常见GC算法
(1)引用计数
(2)标记清除
(3)标记整理
(4)分代回收
引用计算实现原理
1.核心思想:设置引用数,判断当前引用数是否为0
2.引用计数器
3.引用关系改变时修改引用数字
4.引用数字为0时立即回收
function fn(){
num1 = 1
num2 = 2
//由于num1、num2没有设置关键字 所以挂载在window对象下的 引用计数不是0
// 如果加上const关键字声明 只能在这个函数作用域内使用
//一旦调用结束 我们从全局的地方触发 就不能找到num1、num2 那么它们的引用计算就会变成0 GC就是当成垃圾回收
}
fn()
引用计数算法优缺点
优点:
1.发现垃圾时立即回收
2.最大限度减少程序暂停
缺点:
1.无法回收循环引用的对象
function init(){
const obj1 = {}
const obj2 = {}
obj1.name = obj2
obj2.name = obj1
}
init()
2.时间开销大
标记清除算法
1.核心思想:分标记和清除两个阶段完成
2.遍历所有对象,找标记活动对象
3.遍历所有对象,清除没有标记对象
4.回收相应的空间
优点:
1.相对引用计数来说 循环引用不能回收的问题
缺点:
1.垃圾回收时 空间碎片化 不能让空间得到最大的使用
2.不能立即回收垃圾
标记整理算法原理
1.标记整理可以看做是标记清除的增强
2.标记阶段的操作和标记清除一致
3.清除阶段会先执行整理,移动对象位置
4.不能立即回收垃圾
认识V8
1.V8是一款主流的JavaScript执行引擎
2.V8采用即时编译
3.V8内存设限
4.内存分为新生代和老生代
V8垃圾回收策略
1.分代回收
2.空间复制
3.标记清除
4.标记整理
5.标记增量
V8如何回收新生代对象
1.V8内存分配
(1)V8内存空间一分为二
(2)小空间用于存储新生代对象(32M|16M)
(3)新生代指的是存活时间较短的对象
2.新生代对象回收实现
(1)回收过程采用复制算法+标记整理
(2)新生代内存区分为两个等大小空间
(3)使用空间为From,空闲空间为To
(4)活动对象存储于From空间
(5)标记整理后将活动对象拷贝至To
(6)From与To交换空间完成释放
3.回收细节说明
(1)拷贝过程中可能出现晋升
(2)晋升就是将新生代对象移至老生代
(3)一轮GC还存活的新生代需要晋升
(4)To空间的使用率超过25%
V8如何回收老生代对象
1.老生代对象存放在右侧老生代区域
2.64位操作系统1.4G,32操作系统700M
3.老生代对象就是指存活时间较长的对象
4.老生代对象回收实现
(1)主要采用标记清除、标记整理、增量标记算法
(2)首先使用标记清除完成垃圾空间的回收
(3)采用标记整理进行空间优化
(4)采用增量标记进行效率优化
5.细节对比
(1)新生代区域垃圾回收使用空间换时间(空间较小)
(2)老生代区域垃圾回收不适合复制算法(空间大、数据多)
Performance 工具介绍
1.GC的目的是为了实现内存空间的良性循环
2.良性循环的基石是合理使用
3.时刻关注才能确定是否合理
4.Performance提供多种监控方式
内存问题的外在表现
1.页面出现延迟加载或经常性暂停
2.页面持续性出现糟糕的性能
3.页面的性能随时间延长越来越差
监控内存的几种方式
1.浏览器任务管理器 (shift+esc)
(1)可以监控程序运行时DOM与JS堆分别占据的内存空间变化
2.TimeLine时序图记录
(1)可以获取程序运行中JS堆内存的走势图和是否存在频繁的垃圾回收
3.堆快照查找分离DOM
什么是分离DOM
1.界面元素存活在DOM树上
2.垃圾对象时的DOM节点
3.分离状态的DOM节点
4.在界面上不体现 在内存中却真实存在
为什么确定频繁垃圾回收
1.GC工作时应用程序是停止的
2.频繁且过长的GC会导致应用假死
3.用户使用中感知应用卡顿
慎用全局变量
1.全局变量定义在全局执行上下文,是所有作用域链的顶端
2.全局执行上下文一直存在于上下文执行栈,直到程序退出
3.如果某个局部作用域出现了同名变量则会遮蔽或污染全局
缓存全局变量
1.执行效率会更快
function getBtn1() {
let oBtn1 = document.getElementById('btn1')
let oBtn2 = document.getElementById('btn2')
let oBtn3 = document.getElementById('btn3')
let oBtn4 = document.getElementById('btn4')
}
function getBtn2() {
let obj = document
let oBtn1 = obj.getElementById('btn1')
let oBtn2 = obj.getElementById('btn2')
let oBtn3 = obj.getElementById('btn3')
let oBtn4 = obj.getElementById('btn4')
}
通过原型对象添加附加方法
避开闭包陷阱
function foo(){
var el = document.getElementById('btn')
el.onclick = function() {
console.log(el.id)
}
el = null
//如果dom节点在我们程序中消失了 dom的引用就不存在了
//但是el对之前的dom还是有引用的 垃圾回收机制并不会对它回收 就会导致内存泄漏
//如果加上 el = null 我们代码的引用也就消失了
}
避免属性访问方法使用
For循环优化
var aBtns = document.getElementsByClassName('btn')
for (let index = 0; index < aBtns.length; index++) {
console.log(index)
}
// 优化
for (let index = 0; len = aBtns.length index < len; index++) {
console.log(index)
}
选择最优的循环方法
文档碎片优化节点添加
克隆优化节点操作
直接量替换new Object
减少判断层级
减少作用域链查找层级
减少数据读取次数
1.索引值要频繁访问的时候 需要提前存储起来 节约查找时间 使速度更快
2.这样的快建立在消耗内存空间的情况下
字面量与构造式
let test = () => {
let obj = new Object() // 需要调用一个函数
obj.name = 'zce'
obj.age = 38
obj.slogan = '我为前端而活'
return obj
}
let test = () => {
let obj = { // 直接开辟一个空间存储
name:'zce',
age:38,
slogan:'我为前端而活'
}
return obj
}
console.log(test())
// 基本类型创建时 字面量形式要远远高于构造形式
var str1 = 'zce时刻都能'
var str2 = new String('zce时刻都能')
console.log(str1)
console.log(str2)
1.字面量速度要快于构造式
减少声明及语句
采用事件绑定
1.在元素非常多的情况下 for循环一个个绑定点击事件肯定是要比 事件委托要慢许多的