0、预备知识
0.1 认识内存管理
不管什么样的编程语言,在代码的执行过程中都是需要给它分配内存的,不同的是某些编程语言需要我们自己手动的管理内存,某些编程语言会可以自动帮助我们管理内存:
不管以什么样的方式来管理内存,内存的管理都会有如下的生命周期:
- 第一步:分配申请你需要的内存(申请);
- 第二步:使用分配的内存(存放一些东西,比如对象等);
- 第三步:不需要使用时,对其进行释放;
不同的编程语言对于第一步和第三步会有不同的实现:
- 手动管理内存:比如C、C++,包括早期的OC,都是需要手动来管理内存的申请和释放的(malloc和free函数);
- 自动管理内存:比如Java、JavaScript、Python、Swift、Dart等,它们有自动帮助我们管理内存;
我们可以知道JavaScript通常情况下是不需要手动来管理的。
0.2 JS的内存管理
JavaScript会在定义变量时为我们分配内存。但是内存分配方式是一样的吗?
- JS对于
基本数据类型
内存的分配会在执行时,直接在栈空间
进行分配; - JS对于
复杂数据类型
内存的分配会在堆内存
中开辟一块空间,并且将这块空间的指针返回值变量引用;
0.3 JS的垃圾回收
因为内存的大小是有限的,所以当内存不再需要的时候,我们需要对其进行释放,以便腾出更多的内存空间。
在手动管理内存的语言中,我们需要通过一些方式自己来释放不再需要的内存,比如free函数:
- 但是这种管理的方式其实非常的低效,影响我们编写逻辑的代码的效率;
- 并且这种方式对开发者的要求也很高,并且一不小心就会产生内存泄露;
所以大部分现代的编程语言都是有自己的垃圾回收机制:
- 垃圾回收的英文是Garbage Collection,简称GC;
- 对于那些不再使用的对象,我们都称之为是垃圾,它需要被回收,以释放更多的内存空间;
- 而我们的语言运行环境,比如Java的运行环境JVM,JavaScript的运行环境js引擎都会内存 垃圾回收器;
- 垃圾回收器我们也会简称为GC,所以在很多地方你看到GC其实指的是垃圾回收器;
但是这里又出现了另外一个很关键的问题:GC怎么知道哪些对象是不再使用的呢?
- 这里就要用到GC的算法了
0.3.1 常见的GC算法 – 引用计数
引用计数:
- 当一个对象有一个引用指向它时,那么这个对象的引用就+1,当一个对象的引用为0时,这个对象就可以被销毁掉;
- 这个算法有一个很大的弊端就是会产生
循环引用
,比如如下:循环引用的两个obj无法被GC回收
0.3.2 常见的GC算法 – 标记清除
标记清除:
- 这个算法是设置一个根对象(root object)(
在浏览器中,这个根对象就是全局对象GO
),垃圾回收器会定期从这个根开始,找所有从根开始有引用到的对象,对于哪些没有引用到的对象,就认为是不可用的对象; - 这个算法可以很好的解决循环引用的问题;
JS引擎比较广泛的采用的就是标记清除算法,当然类似于V8引擎为了进行更好的优化,它在算法的实现细节上也会结合一些其他的算法。
一、JS中闭包的定义
- 一个普通的函数function,如果它可以访问外层作用于的自由变量,那么这个函数就是一个闭包;
- 从广义的角度来说:JavaScript中的函数都是闭包;
- 从狭义的角度来说:JavaScript中一个函数,如果访问了外层作用于的变量,那么它是一个闭包;
二、闭包的访问过程(内存图)
如果我们编写了如下的代码,它一定是形成了闭包的:
2.1 创建GO对象,创建全局执行上下文并压入执行上下文栈中
2.2 执行foo(),创建foo函数的AO对象
2.3 创建foo函数执行上下文并压入执行上下文栈中,然后开始执行foo函数内的代码
2.4 foo函数执行结束,将foo函数执行上下文弹出栈
2.5 执行fn()函数,创建fn函数(bar)的AO对象,创建fn函数(bar)的函数执行上下文并压入执行上下文栈中
2.6 fn函数(bar)执行完成,将fn函数(bar)的函数执行上下文弹出栈,垃圾回收器销毁bar函数的AO对象
注意:在此之前,是存在内存泄漏的,因为foo函数执行结束了,但是foo函数的AO对象并没有被销毁(因为有bar函数对象指向它)。foo函数的AO对象是应该被销毁的但没被销毁,所以说存在内存泄露!
2.7 执行fn = null,由于fn变量指向null,bar函数的AO对象没有任何变量指向,所以垃圾回收器会销毁bar函数的AO对象
2.8 由于bar函数的AO对象被垃圾回收器销毁,foo函数的AO对象没有任何变量指向,也会被垃圾回收器销毁
2.9 指向foo = null,foo变量指向null,不再指向foo函数对象
2.10 由于foo函数对象没有任何变量指向,所以会被垃圾回收器销毁
三、AO不使用的属性
我们来研究一个问题:AO对象不会被销毁时,是否里面的所有属性都不会被释放?
- 下面这段代码中name属于闭包的父作用域里面的变量;
- 我们知道形成闭包之后count一定不会被销毁掉,那么name是否会被销毁掉呢?
- 这里我打上了断点,我们可以在浏览器上看看结果;
答案:AO不使用的属性name是会被销毁的