JavaScript 性能优化

本文深入解析JavaScript性能优化,包括内存管理的必要性、内存分配与释放,以及垃圾回收机制,重点讲解了V8引擎的分代回收策略和常见算法,同时介绍了如何使用Performance工具监控和解决内存问题。
摘要由CSDN通过智能技术生成

在前端快速发展的今天,如果不能时刻保持学习就会很快被淘汰。分享一下JavaScript 性能优化的相关知识,文章有点长,希望对大家有所帮助。每天进步一点点。

一、性能优化简介

1、不可避免

​ 随着软件开发行业的不断发展,性能优化已经是一个不可避免的话题。从初、中级工程师,到高级、资深工程师,以及技术专家,可以说每个人都在谈性能优化。

2、什么是性能优化

​ 任何一种能够提高运行效率,降低运行开销的行为都可以看做是优化操作

3、前端性能优化

​ 请求资源时所用到的网络以及数据的传输方式,开发过程中所用到的框架等等

在这里谈一下JavaScript语言本身的优化。从认知内存空间的使用,到垃圾回收方式的理解,从而编写出高效的JavaScript代码。

二、内存管理

1、内存为什么需要管理

​ 如果我们在写代码的时候不够了解内存管理机制,就有可能写出一些不容易察觉的内存问题代码,这种代码多了就会给程序带来一些意想不到的bug,所以掌握内存管理是非常有必要的。

2、内存管理介绍

​ 内存:由可读写单元组成,表示一片可操作空间

​ 管理:认为的去操作一片空间的申请、使用和释放

​ 内存管理:开发者主动申请空间、使用空间、释放空间

​ 管理流程:申请-使用-释放

3、JavaScript 内存管理

// 申请内存空间
const obj = {}
// 使用内存空间
obj.name = 'zhagnsan'
// 释放内存空间
obj = null

三、垃圾回收

1、JavaScript 中的垃圾

(1) JavaScript 中内存管理是自动的
(2) 每创建一个对象、数组或者函数的时候,就会自动分配相应的内存空间
(3) 后续代码在执行过程中,通过一些引用关系无法再访问到某些对象的时候,这些对象就被看做是垃圾;
(4) 一些存在的对象,由于代码中不合适的语法或者结构性错误,无法再次找到这些对象,这些对象也是垃圾;
(5) JavaScript 引擎发现垃圾时就会出来工作,把这些垃圾所占据的空间进行回收,这个过程就是 JavaScript 垃圾回收

2、JavaScript 中的可达对象

可以访问到的对象就是可达对象(引用、作用域链)

可达的标准就是从根出发是否能够被找到

JavaScript 中的根可以理解为是全局变量对象,即全局执行上下文

3、JavaScript 中的引用和可达

// 1、引用
let obj = { name: 'zhangsan' }
// { name: 'zhangsan' } 空间 就已经被obj引用,obj在根上可以被找到,obj是可达的
let obj2 = obj 	// { name: 'zhangsan' } 空间 又被 obj2 引用了
obj = null 		// { name: 'zhangsan' } 空间 不再被obj引用,但空间还是可达的
// { name: 'zhangsan' } 空间 可达是因为obj2还在引用
// 2、可达
function objGroup(obj1, obj2) {
	obj1.next = obj2
	obj2.prev = obj1
	return {
		o1: obj1,
		o2: obj2
	}
}
let obj = objGroup({name: 'obj1'}, {name: 'obj2'})
// console.log(obj)
// {
//   o1: <ref *1> {
//     name: 'obj1',
//     next: { name: 'obj2', prev: [Circular *1] }
//   },
//   o2: <ref *2> {
//     name: 'obj2',
//     prev: <ref *1> { name: 'obj1', next: [Circular *2] }
//   }
// }

// 破坏掉obj1的引用,obj1不可达,就会变为垃圾
delete obj.o1
delete obj.o2.prev
console.log(obj)
// {{ o2: { name: 'obj2' } }

可达对象

四、GC算法

1、GC定义与作用

​ 定义:GC就是垃圾回收机制的简写

​ 作用:GC可以找到内存中的垃圾、并释放和回收空间

2、GC里的垃圾是什么

// 1、程序中不再需要使用的对象
function fun () {
	name = 'zhangsan'
	return `${name} is a coder`
}
fun()	// 函数调用完,name就不再需要了

///2、程序中不能再访问到的对象
function fun2 () {
	const name = 'zhagnsan'
	return `${name} is a coder`
}
fun2()	// 函数调用完,外部的空间就访问不到name了

3、GC算法是什么

GC是一种机制,这种机制里面的垃圾回收器可以去完成具体的工作

工作的内容就是查找垃圾释放空间并且回收空间

算法就是垃圾回收器在工作时【查找释放和回收空间】所遵循的规则

4、常见GC算法

(1) 引用计数

A:核心思想是设置引用数,判断当前引用数是否为0

// 引用计数器
// 引用关系改变时修改引用数字
// 引用数字为0时立即回收
const obj1 = {name: 'zhangsan'}
const obj2 = {name: 'lisi'}
const list = [obj1.name, obj2.name]
// list 中存在对obj1和obj2的引用

function fn1 () {
	num1 = 1	// num1是存放在全局的
	num2 = 2
}
fn1()		// 执行完后num1和num2不会被回收
// 以上变量都是挂在到Windows下面的,他们的引用计数都不是0

function fn2 () {
	const num3 = 1	// num3是存放在函数内的
	const num4 = 2
}
fn2()		// 执行完后num3和num4的引用计数为0,将作为垃圾被回收

B:引用计数算法优缺点

// 优点:
// 1、发现垃圾时立即回收(只要引用数为0就立即回收)
// 2、最大限度减少程序暂停(当发现内存即将爆满时,立刻找到引用数为0的进行回收)

// 缺点:
// 1、无法回收循环引用的对象
function fn() {
	const obj1 = {}
	const obj2 = {}
	obj1.name = obj2
	obj2.name = obj1
	return '循环引用示例'
}
fn()
// 2、时间开销大(需要时刻监控引用数是否修改)
(2) 标记清除

A:核心思想是分标记和清除两个阶段完成

// 1、遍历所有对象,对活动对象(可达对象)进行标记
// 2、遍历所有对象,清除没有标记的对象,并将刚刚的标记取消
// 3、回收相应的空间

B:标记清除算法优缺点

// 优点:
// 1、相对于引用计数来说,解决了循环引用对象的回收问题
// 缺点:
// 1、空间碎片化,不能让空间最大化的使用(地址不连续),浪费空间
// 2、不会立即回收垃圾对象
// 3、清除的时候程序是暂停工作的
(3) 标记整理

A:标记整理可以看做是标记清除的增强

// 标记阶段操作和标记清除一致
// 清除阶段会先执行整理,移动对象位置

B、标记整理算法优缺点

// 优点:减少碎片化空间
// 缺点:不会立即回收垃圾对象
(4) 分代回收

V8中使用分代回收机制

五、V8引擎的垃圾回收

1、认识V8

// (1) V8是一款主流的 JavaScript 执行引擎(Chrome浏览器、node平台等都是采用V8引擎执行代码)
// (2) V8有一套优秀的内存管理机制
// (3) V8采用即时编译(直接将源码转为可以直接执行的机器码)
// (4) V8内存设限【64位:1.5G,32位:800M】(原因1:V8引擎用于浏览器,原因2:垃圾回收机制)
// (5) V8采用分带回收思想实现垃圾回收
// (6) V8内存空间一分为二,分为新生代(64位:32M,32位:16M)和老生代(64位:1.4G,32位: 700M)

2、V8垃圾回收策略

这里提到的回收主要是回收存放在堆区里面的对象数据
1、采用分代回收的思想
2、内存分为新生代、老生代
3、针对不同对象采用不同算法

V8垃圾回收策略

3、V8中常用GC算法

// 1、分代回收
// 2、空间复制
// 3、标记清除
// 4、标记整理
// 5、标记增量

4、V8回收新生代对象

// 新生代说明:
// 新生代对象存放在左侧新生代区域
// 小空间【不超过800M】用于存储新生代对象(64位:32M,32位:16M)
// 新生代指的是存活时间较短的对象(例如函数作用域中的变量,块级作用域中的变量)
// 回收实现:
// 1、回收过程采用复制算法 + 标记整理
// 2、将新生代内存分为两个等大小的空间From和To
// 3、From空间叫做使用状态【活动对象存储于From空间】,To空间叫做空闲状态
// 4、代码执行时如果需要申请空间,就将变量分配至From空间
// 5、当From空间存储到一定大小后就会触发GC操作
// 6、采用标记整理的操作对From空间进行活动对象的标记
// 7、找到活动对象后继续使用整理的操作,再去把他们的位置变得连续
// 8、然后将活动对象拷贝至To空间
// 9、拷贝完成后,From与To交换空间完成释放
// 回收细节:
// From拷贝到To过程中可能出现晋升
// 晋升就是将新生代对象移动至老生代
// 晋升的条件:
// 1、一轮GC还存活的新生代需要晋升
// 2、To空间的使用率超过25%

5、V8回收老生代对象

// 老生代说明:
// 老生代对象存放在右侧老生代区域
// 大空间【不超过1.5G】用于存储老生代对象(64位:1.4G,32位:700M)
// 老生代指的是存活时间较长的对象(例如全局变量、闭包中的变量)
// 回收实现:
// 1、采用标记清除(为主)、标记整理、增量标记算法
// 2、首先使用标记清除完成垃圾空间的回收
// (1)找到老生代存储区域中的所有活动对象进行标记
// (2)释放掉垃圾对象的空间,会存在空间碎片化的问题【相对于这个问题,速度提升更为明显】
// 3、当新生代区域的内容移动至老生代时,而且老生代的存储空间又不足以存放移动过来的对象
// 4、这种情况下就会采用标记整理进行空间优化
// 5、采用增量标记进行效率优化
// 与新生代回收细节对比:
// 1、新生代区域回收使用空间换时间(使用复制算法,意味着时刻都会存在空闲空间,但是新生代本身空间就比较小,所以空间浪费相比于时间的提升,这点浪费是微不足道的)
// 2、老生代区域垃圾回收不适合复制算法(老生代的空间区域比较大,如果一分为二会有几百兆的空间浪费不用的;而且老生代区域中存放的数据比较多,复制消耗的时间就会比较多)

标记增量优化垃圾回收效率:

​ 将一整段的垃圾操作给拆分成多个小部分,组合完成整个垃圾回收,替代之前一口气做完的垃圾回收操作。这样的好处就是实现垃圾回收和程序执行交替完成,把之前很长一段时间的停顿时间拆分成很短的几段,时间消耗更合理一些,用户体验更友好一些。

​ 整个V8最大的垃圾回收(垃圾达到1.5G)采用非增量标记的形式进行垃圾回收时间也不超过1秒,使用标记增量将时间拆分成好几段的时间间隔也是合理的。

增量标记

六、Performance工具

1、工具介绍

// 为什么使用Performance?
// 1、GC的目的是为了实现内存空间的良性循环
// 2、良性循环的基础就是在写代码的时候能够合理利用内存空间
// 3、要知道内存使用是否合理就需要时刻关注内存变化
// 4、Performance就提供了更多监控方式在程序运行时,时刻监控内存
// Performance使用步骤:
// 1、打开浏览器输入目标网址
// 2、进入开发人员工具面板,选择Performance【性能】
// 3、开启录制功能,访问具体界面
// 4、执行用户行为,一段时间后停止录制
// 5、分析界面中记录的内存信息

2、内存问题体现

// 1、页面出现延迟加载或经常性暂停
// 2、页面持续性出现糟糕的性能
// 3、页面的性能随时间延长越来越差

3、监控内存

A:界定内存问题的标准

// 1、内存泄漏:内存使用持续升高
// 2、内存膨胀:在多数设备上都存在性能问题
// 3、频繁垃圾回收:通过内存变化图进行分析

B:监控内存的几种方式

// 1、浏览器任务管理器监控内存【发现问题】
// 打开浏览器,Shift + Esc 调出任务管理器
// 定位到当前活动页面执行的脚本,右击选中 JavaScript 内存
// 内存列指的是DOM节点所占据的内存,如果数据在增大,说明在创建新的DOM节点
// JavaScript 内存列表示JavaScript的堆,小括号里面的值是当前界面所有可达对象正在使用的内存大小

// 2、Timeline时序图记录内存【定位问题】
// 打开浏览器【例如Chrome】,右击检查,点击【Performance】性能进入性能面板
// 点击计时操作,开始录制
// 操作页面,点击停止按钮,停止录制
// 上方内存没有勾选的话需要勾选
// 选择自己要分析的数据【JS Heap、Documents、Nodes、Listeners、CPU Memory】
// 发现问题后可以直接找到相应的时间节点,拖动查看页面显示
// 配合界面的变化显示,找到我们做了什么导致出现的问题

// 3、对快照查找分离DOM
// (1) 工作原理:找到 JS Heap(JS堆),对它进行照片留存
// (2) 什么是分离DOM?
// 界面元素存活在DOM树上
// 垃圾对象的DOM节点(从DOM树上脱离即页面上看不见,而且JS代码中也没有引用)
// 分离状态的DOM节点(从DOM树上脱离即页面上看不见,但是内存中占用空间,这就是内存泄漏)
// (3) 查找分离DOM:
// 打开浏览器,右击检查,点击内存【Memory】进入内存面板
// 点击 Heap snapshot【堆快照】
// 操作页面,点击下方Take snapshot 获取堆快照
// 在筛选框中输入要检查的DOM节点
// 查看是否存在分离DOM浪费内存

// 4、判断是否存在频繁的垃圾回收
// (1) 为什么确定频繁垃圾回收?
// GC工作时应用程序是停止的
// 频繁且过长的GC会导致应用假死
// 用户使用中感知应用卡顿
// (2) 确定频繁的垃圾回收:
// Timeline 中频繁的上升下降
// 任务管理器中数据频繁的增加减小
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值