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()
- JS引擎在执行全局代码之前,会提前堆内存创建一个全局对象(GO),并绑定内置的一些对象,如Numner,String,SetTimeout等等。
- 接着会解析全局代码,对函数foo解析到第9行结束的过程中,首先会在GO对象中声明函数foo,然后在堆内存中创建一个函数对象。接着给这个对象添加上级作用域,函数的执行体。最后在给GO对象中的foo绑定对象的地址0xa00。
-
接着执行全局代码的时候,在第10行,首先会执行foo()函数,执行函数之前,ECS中会添加一个FEC(函数执行上下文),然后在执行前会为函数foo创建一个AO对象,并且为其内部的函数bar创建一个函数对象,如图:
-
接着将函数bar的地址赋给GO中的fn,完成第10行的代码执行。
-
此时 FEC(foo) 已经执行完毕,会从ECS调用栈中弹出,在堆内存中AO对象也需要销毁,但是bar函数仍然引用着AO对象中的name和age属性,导致无法销毁,这中状态就是闭包。