JS性能优化——JavaScript语言的优化
2020-11-11 本文已影响0人
yapingXu
拉勾大前端的学习笔记,仅作为学习记录
内容概要
- 内存管理
- 垃圾回收与常见GC算法
- V8引擎的垃圾回收
- Performance工具
- 代码优化实例
内存管理
- 内存: 由可读写的单元组成,表示一片可操作的空间
- 管理:人为的去操作一片空间的申请,使用和释放
- 内存管理:开发者主动申请空间、使用空间、释放空间
- 管理流程:申请-使用-释放
Js中的内存管理
- 申请内存空间
// 申请
let obj = {}
// 使用
obj.name="lg"
// 释放
obj = null
Js中的垃圾回收
什么样的内容会被当做垃圾
因为js中内存管理是自动的,所以当对象不再被引用,对象不能从根上访问到,都是垃圾
可达对象
可以通过引用、作用于链访问到的对象就是可达对象
可达的标准是从根出发是否能够被找到
js中根就可以理解为是全局变量对象
垃圾回收
找到垃圾,让js的执行引擎来进行空间的释放和回收
js中的引用和可达
待补充
GC回收机制
GC的定义和作用
- GC就是垃圾回收机制的简写
- GC可以找到内存中的垃圾、并释放和回收空间
GC里面的垃圾是什么
- 程序中不再需要使用的对象
function func(){
name = 'lg'
console.log(`${name} is a coder`)
}
func()
- 程序中不能再访问到的对象
function func(){
const name = 'lg'
console.log(`${name} is a coder`)
}
func()
GC算法是什么
- GC是一种机制,垃圾回收器完成具体工作
- 工作的内容就是查找垃圾,释放空间,回收空间
- 如何查找空间 ?
- 释放空间的时候应该怎样去释放 ?
- 回收空间的时候要怎样进行去分配 ?
- 算法就是工作时查找和回收所遵循的规则
常见的GC算法
- 引用计数 : 通过一个数字判断当前对象是不是一个垃圾
- 标记清除: 在GC工作时给活动对象加标记判断对象是否是垃圾
- 标记整理:和标记清除类似,只不过在后续回收过程会做一些事情
- 分代回收:在V8中会用到的回收机制
引用计数算法实现原理
核心思想:设置引用数,判断当前引用数是否为0
算法规则:当某个对象引用关系发生改变的时候,引用计数器会去修改这个对象所对应的引用数值,引用数字为0时立即回收
优点:
- 发现垃圾立即回收
- 最大限度减少程序暂停
缺点:
- 无法回收循环引用的对象
function func(){
const obj1 = {}
const obj2 = {}
obj1.name = obj2
obj2.name = obj1 // 当垃圾回收obj1的时候会发现obj1被obj2所引用,obj1的引用计数不为0,所以GC引用计数算法下,obj1不能被回收
}
func()
- 时间开销大
标记清除算法的实现原理
核心思想:分标记和清除两个阶段
- 标记阶段: 遍历所有对象找到并标记活动对象(也就是可达对象)
- 清除阶段:遍历所有对象清除没有被标记的对象,把第一阶段的标记抹掉方便GC继续下次工作
最后,把回收的空间放到空闲列表上面,方便后面的程序直接申请空间使用
优点:
- 可以解决对象循环引用的回收操作
缺点:
- 回收空间地址不连续,空间的碎片化:由于当前我们回收的对象在地址上是不连续的,从而造成回收后他们分散在各个角落,一旦新的空间申请大于或者小于当前的空间碎片,会造成空间的浪费或空间不足
- 不能立即回收垃圾对象
标记整理算法实现原理
标记整理可以看做是标记清除的增强
标记阶段的操作和标记清除一致
清除阶段会先执行整理,移动对象位置
优点
- 减少碎片化空间
缺点
- 不能立即回收垃圾对象
认识V8
- 主流的js执行引擎
- 采用即时编译,之前很多js引擎都需要将代码转为字节码,然后才能去执行,V8是直接将代码编译为可执行的机器码,速度上快了很多
- V8内存设有上限,64位操作系统上限不超过1.5G,32位系统不超过800M
V8垃圾回收策略
- 采用分代回收的思想
- 内存分为新生代、老生代
- 针对不同代的算法采用不同的GC算法
V8中常用的GC算法
- 分代回收
- 空间复制
- 标记清除
- 标记整理
- 标记增量
V8如何回收新生代对象
V8内存分配
新生代对象(64位32M | 32位16M) / 老生代的对象
新生代指的是存活时间较短的对象
新生代对象的回收实现
主要用复制算法+ 标记算法,将储存新生代对象的空间分为两个等大小的空间,使用空间叫做From ,空闲空间叫做To
- 所有的所动对象都在From里面
- 当From存储到一定量时,触发GC操作
- 标记整理后,把标记的活动对象copy到To
- 然后把To Copy到From,From到To进行空间置换,达到From的空间释放
细节说明 - copy过程发现变量晋升,
晋升含义: 某个对象使用空间在老生代对象中也出现过,将新生代移到老生代储存
晋升触发时机: - 一轮GC后还存活的新生代需要晋升
- To空间的使用率超过25%
老生代回收说明
老生代对象说明
- 存在右侧的老生代区,64位1.4G,32位700M
- 存活时间较长的对象
回收实现
标记清除,标记整理,增量标记算法
步骤
- 首先标记清除
- 当想把新生代放入老生代的时候,并且当老生代空间不足的时候,触发标记整理
- 最后采用增量标记进行效率优化
细节对比
- 新声带区域垃圾回收就是使用空间换时间
- 老生代不适合复制算法(耗时,空间占用大)
代码优化相关
- 慎用全局变量
- 缓存全局变量
function getBtn(){
let btn1 = document.getElementById('btn1')
let btn2 = document.getElementById('btn2')
}
function getBtn(){
let doc = document
let btn1 = doc.getElementById('btn1')
let btn2 = doc.getElementById('btn2')
}
- 通过原型对象增加附加方法
function F1(){
this.foo = function (){
console.log(111)
}
}
f1 = new F1()
function F2(){}
F2.property.foo = function (){
console.log(111)
}
f2 = new F2()
- 避开闭包陷阱
function foo(){
let el = document.getElementById('btn')
el.onclick = function(){
console.log(el.id)
}
el = null // 在函数内部删除对dom的引用,从而避免内存泄漏
}
foo()
- 避免属性访问方法使用
js不需要属性的访问方法,所有属性都是外部可见的
使用属性访问方法只会增加一层重定义,没有访问的控制力
function Person(){
this.name = "coder"
this.age = 18
this.getAge = function (){
return this.age
}
}
const p1 = new Person()
const pAge = p1.getAge()
function Person(){
this.name = "coder"
this.age = 18
}
const p1 = new Person()
const pAge = p1.age
- For循环优化
const btns = document.getElementByClass('.btn')
for(var i ; i< btns.length;i++){
console.log(i)
}
for(var i ;len=btns.length; i< len;i++){
console.log(i)
}
- 采用最优循环方式
for / forEach / for...in
var arrList = [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.createElement('p')
for ( var i = 0; i<10; i++ ){
var oP = document.createElement('p')
oP.innerHTML = i
fragEle.appendChild(oP)
}
document.body.appendChild(fragEle)
- 克隆优化节点操作
当新增节点的时候,可以找当前已经存在的相似节点clone后添加到界面上
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操作
当定义一些对象和数组的时候,可以直接通过new的方式 ,也可以通过字面量
const a = new Array()
a[0] = 1
a[1] = 2
a[2] = 3
const a = [1,2,3]