02_JS内存管理和闭包
JS的内存管理
不管什么样的编程语言,在代码的执行过程中都是需要给它分配内存的,不同的是某些编程语言需要我们自己手动的管理内存,某些编程语言会可以自动帮助我们管理内存。
不同的编程语言对于第一步和第三步会有不同的实现:
手动管理内存:比如C、C++,包括早期的OC,都是需要手动来管理内存的申请和释放的(malloc和free函数);
自动管理内存:比如Java、JavaScript、Python、Swift、Dart等,它们有自动帮助我们管理内存;
JS对于基本数据类型内存的分配会在执行时,直接在栈空间进行分配;
JS对于复杂数据类型内存的分配会在堆内存中开辟一块空间,并且将这块空间的指针返回值变量引用;
垃圾回收器 Garbage Collection(GC)
JS的垃圾回收:因为内存的大小是有限的,所以当内存不再需要的时候,我们需要对其进行释放,以便腾出更多的内存空间。
常见的GC算法 – 引用计数
当一个对象有一个引用指向它时,那么这个对象的引用就+1,当一个对象的引用为0时,这个对象就可以被销毁掉;
这个算法有一个很大的弊端就是会产生循环引用;
常见的GC算法 – 标记清除
这个算法是设置一个根对象(root object),垃圾回收器会定期从这个根开始,找所有从根开始有引用到的对象,对于哪些没有引用到的对象,就认为是不可用的对象;
这个算法可以很好的解决循环引用的问题;
JS中函数是一等公民
在JavaScript中,函数是非常重要的,并且是一等公民:
那么就意味着函数的使用是非常灵活的;
函数可以作为另外一个函数的参数,也可以作为另外一个函数的返回值来使用;
自己编写高阶函数
使用内置的高阶函数
JS中闭包的定义
在计算机科学中对闭包的定义(维基百科):
闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures);
是在支持 头等函数 的编程语言中,实现词法绑定的一种技术;
闭包在实现上是一个结构体,它存储了一个函数和一个关联的环境(相当于一个符号查找表);
闭包跟函数最大的区别在于,当捕捉闭包的时候,它的 自由变量 会在捕捉时被确定,这样即使脱离了捕捉时的上下文,它也能照常运行;
理解和总结:
一个普通的函数function,如果它可以访问外层作用于的自由变量,那么这个函数就是一个闭包;
从广义的角度来说:JavaScript中的函数都是闭包;
从狭义的角度来说:JavaScript中一个函数,如果访问了外层作用于的变量,那么它是一个闭包;
makeAdder函数执行完毕,正常情况下我们的AO对象会被释放。但是因为在0xb00的函数中有作用域引用指向了这个AO对象,所以它不会被释放掉;
闭包的内存泄露
为什么经常会说闭包是有内存泄露的呢?
在上面的案例中,如果后续我们不再使用add10函数了,那么该函数对象应该要被销毁掉,并且其引用着的父作用域AO也应该被销毁掉;
但是目前因为在全局作用域下add10变量对0xb00的函数对象有引用,而0xb00的作用域中AO(0x200)有引用,所以最终会造成这些内存都是无法被释放的;
所以我们经常说的闭包会造成内存泄露,其实就是刚才的引用链中的所有对象都是无法释放的;
怎么解决这个问题呢?
因为当将add10设置为null时,就不再对函数对象0xb00有引用,那么对应的AO对象0x200也就不可达了;
在GC的下一次检测中,它们就会被销毁掉;
注意:闭包中AO对象不会被销毁时,AO不使用的属性,在V8引擎优化中会被销毁