JS内存管理和闭包

102 阅读3分钟

1.认识内存管理

不论什么样的编程语言,在执行代码的时候,都需要分配内存的。区别在于有些是手动管理,有些是自动管理

  • 第一步:申请内存
  • 第二步:使用内存(存放)
  • 第三步:不使用时,释放内存

JavaScript是不需要手动管理的

2.JS的内存管理

JavaScript是在定义变量的时候为我们分配内存的。

但是不同的数据类型,分配方式不同

  • 基本数据类型内存的分配会在执行时,直接在栈空间进行分配
  • 复杂数据类型内存的分配会在堆内存中开辟一块空间,并且将这块空间的指针返回值给变量引用

3. JS的垃圾回收

内存大小是有限的,当内存不需要的时候,我们需要对内存进行释放。垃圾回收的英文是Garbage Collection,简称GC。而我们的语言运行环境,比如Java的运行环境JVM,JavaScript的运行环境js引擎都会内存垃圾回收器

但是JS引擎是如何知道哪些内存是需要释放的呢?

GC算法

1. 引用计数

  • 当有一个对象引用指向它时,这个对象的引用+1,当一个对象的引用为0时,这个对象就可以被销毁了。
  • 有个弊端就是会产生循环引用 A<==>B,A与B互相引用。

2. 标记清除

设置一个根对象,找从根开始有引用的对象,剩下的没有引用的就被认为不可用对象,可以销毁。

4.JS闭包的定义(MDN)

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

5. 闭包的访问过程

function foo() {
  let name = "foo"
  let age = 18
  function bar() {
    console.log(name)
    console.log(age)
  }
  return bar
}
var fn = foo()
fn()
  1. JS引擎在执行全局代码之前,会提前堆内存创建一个全局对象(GO),并绑定内置的一些对象,如Numner,String,SetTimeout等等。

image.png

  1. 接着会解析全局代码,对函数foo解析到第9行结束的过程中,首先会在GO对象中声明函数foo,然后在堆内存中创建一个函数对象。接着给这个对象添加上级作用域函数的执行体。最后在给GO对象中的foo绑定对象的地址0xa00。

image.png

  1. 接着执行全局代码的时候,在第10行,首先会执行foo()函数,执行函数之前,ECS中会添加一个FEC(函数执行上下文),然后在执行前会为函数foo创建一个AO对象,并且为其内部的函数bar创建一个函数对象,如图: image.png

  2. 接着将函数bar的地址赋给GO中的fn,完成第10行的代码执行。 image.png

  3. 此时 FEC(foo) 已经执行完毕,会从ECS调用栈中弹出,在堆内存中AO对象也需要销毁,但是bar函数仍然引用着AO对象中的name和age属性,导致无法销毁,这中状态就是闭包。

image.png