(深入JavaScript 二)js的内存管理、js的垃圾回收、闭包
js内存管理
js的内存管理大致都会有以下三个生命周期:
- 申请需要使用的内存
- 使用分配的内存
- 不需要使用、对其释放
但对于JavaScript这门语言,上面对于内存的管理是不需要我们手动管理的。在我们定义变量的时候,JavaScript会为我们自动分配内存,但是对于不同的变量类型,分配内存的方式是不一样的。对于基本数据类型的内存的分配在执行时,会直接在栈空间进行分配,对于复杂数据类型内存的分配会在堆内存中开辟一片空间,并将该空间的指针返回给定义的变量。
js垃圾回收
我们在运行js代码的时候,会占用电脑内存,而电脑内存大小是有限的,对于部分代码不再使用的时候,我们也应该将其运行占用的内存也给释放掉,以便腾出更多的空间。根据这一需求,于是就有了一种机制,垃圾回收机制(Garbage Collection,简称GC)。 在JavaScript中,不需要我们手动去进行回收和处理,JavaScript的运行环境js引擎都自动进行内存垃圾回收。而如何让GC知道哪些对象能进行回收,这就需要用到GC的算法了。
GC算法-引用计数
当一个对象有一个引用指向它的时候,那么这个对象的引用计数+1,当这个对象的引用计数为0的时候,就说明在程序中没有任何代码使用到了这个对象,那么这个对象就会被进行回收。 但引用计数有个问题,那就是对象之间的相互引用,会让这部分空间始终无法都到回收,如下图所示。
GC算法-标记清除法
这个算法是设置一个根对象(root object),垃圾回收器会定期的从这个对象开始,寻找从根开始被引用的对象,而对于没被引用的对象就会被回收,这个算法就很好的解决了引用计数法中对象间相互引用导致的问题了。如下所示,A为根对象,BCDEFG都为可达,是能被根对象寻找到的引用对象,而MN不可达,所以就会被垃圾回收器给回收掉。
备注:垃圾回收机制是通过轮询的方式通过算法进行回收的,期间也不存在固定的每隔多少时间进行回收,全靠算法得出的多少时间进行回收,这一做也是因为垃圾回收时也是会消耗部分性能的,而为了尽量减少这部分性能的开销所以采取这样的处理。
闭包
闭包的形成
先看看闭包是怎么形成的:
function fun1(){
let name = 'kobe'
return function fun2(){
console.log(name)
}
}
let fun = fun1()
在上面代码中,fun1函数里面定义了一个fun2并将它作为返回值返回出去,根据函数执行后会进行销毁,而销毁的同时也会销毁自身及自身作用域链和AO对象(解析函数时产生的对象),而name也包含其中,但是在外部我们定义了变量fun,此时fun等同于fun2函数,当我们调用fun时也会在控制台中正常打印出name的值,那么说明此时的name并没有随着fun1函数的销毁而被销毁掉,这时就已经产生了闭包。但根据上面我们提到的垃圾回收机制,name还存在说明此时对AO的引用就还没有消失,也就是此时AO对象还存在,但实际上,如果我们在fun1中再定义一个变量age,通过debugger后,在浏览器的scope中查看,依然只能查看到name,而没有age,这说明此时能访问到的name并不是AO对象中的name,而是根据函数内部对外部变量的引用,将作用域链进行过滤产生的作用域链子集,形成的一个函数可以访问的外部环境,这个外部环境包含了函数内所有对外部变量的引用。
简单来说闭包的机制,还是看上面的代码,就是在函数fun2内使用到了函数fun1定义的变量,在fun1被销毁后需要让fun2依然能访问到name,就需要将这个变量打包给fun2,让fun2也能在fun1被销毁后依然能正常执行。
闭包的产生
闭包是在什么时候产生呢,实际上很多js引擎都会对函数进行懒解析,什么叫懒解析呢?就是在你调用这个函数的时候才会给你进行解析,你不调用这个函数它就不会给你解析,这样也避免了性能浪费。在懒解析的时候,会对代码进行AST扫描,将函数内部使用到的外部定义的变量进行打包,放到该函数的作用域上,这样,即使父函数被销毁了也能保证子函数的正常运行。
闭包的缺点
其实从上面我们可以大概的看到,当形成闭包后,闭包内的这些外部引用变量都不会被释放掉,不会被GC给回收,还是看上面的代码,当name是一个很大的对象的时候,这时候形成闭包,那么name这个大对象就一直不会被释放掉,就会造成内存泄漏,对于上面代码解决这一问题也很简单,将fun=null,那么这部分内存就会被释放掉,因为这样在根对象的可达对象中将不存在对name的引用,那么name就会被GC回收。
转载自:https://juejin.cn/post/7236635197072343096