文章说明:本专栏内容为本人参加【拉钩大前端高新训练营】的学习笔记以及思考总结,学徒之心,仅为分享。如若有误,请在评论区支出,如果您觉得专栏内容还不错,请点赞、关注、评论。共同进步!
上一篇:【快速了解TypeScript语言】
本篇主要内容时JavaScript的性能优化内容,包括:内存管理、GC算法介绍、V8引擎等
性能优化时不可避免的
哪些内容可以看做是性能优化
任何一种可以提升程序运行效率,降低程序开销的行为,我们都可以看做是一种优化操作。这就意味着在软件开发的过程中,必然存在着很多值得优化的地方。
无处不在的前端性能优化
特别是在前端开发过程中,性能优化时无处不在的,例如请求资源时的网络、数据的传输方式,开发过程中所使用的的框架等。
本篇的核心是JavaScript语言的优化,具体来说就是认知内存空间的使用,垃圾回收的方式介绍。从而可以让我们编写出高效的JavaScript代码。
内容概要:
Memory Management
随着近些年硬件技术的不断发展,同时高级编程语言中也都自带了GC(Garbage Collection)机制,这样的变化,让我们在不需要注意内存使用的情况下,也能够正常的完成相应的功能开发。
function fn() {
arrList = [];
arrList[100000] = 'Leo is a coder'
}
fn()
上述函数体内定义一个数组,数组长度足够大,为了当前函数在调用的时,程序可以向内存申请比较大的内存空间。执行函数过程中,我们使用性能检测工具,我们会发现,内存变化如下,内存持续升高,且并没有回落,这就是内存泄漏。内存泄漏会导致我们的页面处于卡顿状态,因此需要对内存进行人为管理。
JavaScript中的内存管理
和其他语言相通,JavaScript内存管理的流程也是申请内存空间-使用内存空间-释放内存空间。但是由于ECMAScript中并没有提供操作内存的相关API,所以JavaScript语言不能像C或者C++那样,由开发者主动去调用相应的API来完成内存管理。不过,我们仍然可以通过js脚本去演示当前空间的生命周期是怎样完成的。
// 申请空间
let obj = {}
// 使用空间
obj.name = 'Leo'
// 释放空间
obj = null
JavaScript中的垃圾
JavaScript中的可达对象
引用说明代码示例:
let obj = {name:'leo'}; // obj引用leo对象,全局可达
let bai = obj; // bai引用leo内存地址
obj = null; // obj不再引用,但bai依然在引用
可达说明代码示例:
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: {name: 'obj1', next: {name: 'obj2', prev: [Circular]}},
// o2: {name: 'obj2', prev: {name: 'obj1', prev: [Circular]}},
// }
可达对象图示
如果我们在代码中做一些操作,比如使用delete将obj上的o1的应用以及o2中对obj1的应用删除掉,那么出现下面的情况:
程序中不再需要使用的对象
function func() {
name = 'leo';
return `${name} is a coder`
}
func()
上面例子中,当我们函数调用完成后,name不再被需要,因此它成为了一个垃圾
程序中不能再访问到的对象
function func() {
const name = 'leo';
return `${name} is a coder`
}
func()
上面例子中,由于使用了const关键字进行声明变量,因此当函数执行结束后,外界无法再访问到它,它也会成为一个垃圾。
常见的GC算法有以下几种:
所谓的引用计数法就是给每个对象一个引用计数器,每当有一个地方引用它时,计数器就会加1;当引用失效时,计数器的值就会减1;任何时刻计数器的值为0的对象就是不可能再被使用的。
这个引用计数法时没有被Java所使用的,但是python有使用到它。而且最原始的引用计数法没有用到GC Roots。
优点:
缺点:
该算法分为标记和清除两个阶段。标记就是把所有活动对象都做上标记的阶段;清除就是将没有做上标记的对象进行回收的阶段。
优点:
缺点:
标记-整理算法与标记-清理算法类似,只是后续步骤是让所有存活的对象移动到一端,然后直接清除掉端边界以外的内存。
优缺点:该算法可以有效的利用堆,但是整理需要花比较多的时间成本
V8 使用了分代和大数据的内存分配,在回收内存时使用精简整理的算法标记未引用的对象,然后消除没有标记的对象,最后整理和压缩那些还未保存的对象,即可完成垃圾回收。
V8中常用的GC算法
年轻分代中的对象垃圾回收主要通过Scavenge算法进行垃圾回收。在Scavenge的具体实现中,主要采用了Cheney算法:通过复制的方式实现的垃圾回收算法。它将堆内存分为两个 semispace,一个处于使用中(From空间),另一个处于闲置状态(To空间)。当分配对象时,先是在From空间中进行分配。当开始进行垃圾回收时,会检查From空间中的存活对象,这些存活对象将被复制到To空间中,而非存活对象占用的空间将会被释放。完成复制后,From空间和To空间的角色发生对换。在垃圾回收的过程中,就是通过将存活对象在两个 semispace 空间之间进行复制。
年轻分代中的对象有机会晋升为年老分代,条件主要有两个:一个是对象是否经历过Scavenge回收,一个是To空间的内存占用比超过限制。
V8内存分配
新生代对象回收实现
对于年老分代中的对象,由于存活对象占较大比重,再采用上面的方式会有两个问题:一个是存活对象较多,复制存活对象的效率将会很低;另一个问题依然是浪费一半空间的问题。为此,V8在年老分代中主要采用了Mark-Sweep(标记清除)标记清除和Mark-Compact(标记整理)相结合的方式进行垃圾回收。
老年代对象回收实现
老生代与新生代回收对象细节对比:
增量标记优化垃圾回收
图示中,程序在标记阶段被暂停运行,等待标记完成自动运行,当遇到大块需要标记的对象时,程序需要暂停很长一段时间,对用户体验很不友好,因此采用增量标记,将一大块分解为多个小块进行标记,减少每次程序暂停的时长,优化用户体验。最后标记完成后统一进行回收。
今日分享就到了这里,上面很多的概念性问题,要完全的理解并使用这些新的知识,需要很长一段时间。多用、多查、多做!
下面一节我们讲一讲JavaScript的性能优化问题中工具的使用以及代码优化实例记录:2020/11/11