JS内存管理与闭包

87 阅读3分钟

前言

首先,我们应该知道,JavaScript是一个无需手动管理内存的一个编程语言。JS会再定义变量的时候为我们分配好内存,对于基本数据类型内存的分配会在执行时直接在栈空间进行分配;JS对于复杂数据类型内存变量会在堆内存中开辟出一块空间,并将这块空间的地址返回给变量引用。

image.png

JS的垃圾回收

垃圾回收的名称为Garbage Collection,简称GC,其中对于那些不再使用的对象,我们都称之为垃圾,需要被回收。在JS中,JS引擎有垃圾回收器(GC)。 那么,GC又是怎么确定哪些对象需要回收的呢?

GC算法

引用计数

当一个对象有一个引用指向它时,那么对这个对象的引用就加1,只要它的引用值不为0,就说明这个对象还是在被使用的,反之代表需要被回收销毁。但这个算法有一个弊端:会产生循环引用(两个对象互相指向)导致内存泄露。

标记清除

这个算法是设置一个根对象(在js中这个根对象就是GO),垃圾回收器会定期从这个根开始,找所有从根开始有引用到的对象,对于哪些没有引用到的对象,就认为是不可用的对象,应该回收销毁。该算法能解决循环引用的问题。

JS引擎采用的GC算法

现如今JS引擎大多数采用的是标记清除算法,例如V8引擎就是在标记清除算法的基础上进行了一些优化。

闭包(Closure)

首先我们知道在JS中,函数是非常重要的,是一等公民(意味着函数可以非常灵活,可以作为参数,也可以作为返回值),而在像JAVA中,一等公民是对象。

JS中闭包的定义

在维基百科中对闭包的定义是:

  • 闭包在实现上是一个结构体,存储了一个函数和一个关联的环境(相当于一个符号查找表)
  • 闭包跟函数最大区别在于,在捕捉闭包时,它的自由变量(即被函数引用的变量)会在捕捉时被确定,这样即使脱离了捕捉时的上下文,它也能照常运行。

MDN中对JS闭包的解释:

  • 一个函数和对其周围状态(词法环境)的引用捆绑在一起,这样的组合就是闭包
  • 闭包可以让你在一个内层函数中访问到其外层函数的作用域
  • 在JS中,每当创建一个函数,闭包也会随函数创建而创建出来

通过两个比较官方的定义,我们可以得出,JS中的一个函数,如果访问了外层作用域的变量,那么它就是一个闭包。 例如下图,这样就会形成闭包:

image.png

闭包的内存泄露

就是在后续不再使用闭包时,那么该函数应该被销毁掉,而且其引用着的OA也应该跟着被销毁掉,我们所说的闭包的内存泄露,就是因为在全局作用域下有对这个闭包的引用,导致闭包那个函数以及其引用的那个作用域OA都无法释放。

那么如何解决这个闭包内存泄露呢? 只需要把其值设置为null即可

AO中不使用的属性

即闭包中那个被引用的作用域中的变量,是否属于那个作用域中的所有变量都不会被销毁还是只有被函数中使用了那个变量才不会被销毁? 结果是JS引擎会自动将没有使用的变量给销毁掉,只留下使用中的变量,即AO中不使用的属性会被销毁掉