JavaScript的内存管理和闭包
认识内存管理
-
不管什么样的编程语言,在代码的执行过程中,都要给它分配内存的,不同的是有些编程语言需要 手动的管理内存,有些编程语言则是 自动的帮助我们管理内存
-
不管以什么样的方式管理内存,内存的管理都会有以下几个生命周期
- 第一步:分配申请你需要的内存
- 第二步:使用分配的内存
- 第三步:不需要使用时,对其进行释放
JavaScript的垃圾回收
-
因为内存的大小 是有限的,所以当一些存放在内存中的数据不再使用的时候,我们就需要 对其进行释放,以便 腾出更多的内存空间
-
手动管理内存的语言中,释放内存是十分低效的
-
所以大部分 现代的编程语言都是有自己的垃圾回收机制
- 垃圾回收机制 Garbage Collection 简称GC
- 对于那些不再使用的对象,我们都称之为垃圾,需要进行回收
-
但是GC怎么知道哪些对象不会再进行使用呢
常见的GC算法–引用计数(Reference counting)
- 当 一个对象有一个引用指向它时,那么这个对象的引用就会+1
- 当这个对象的引用为0的时候,这个对象就会被销毁
- 但是有一个很大的弊端,就是会产生循环引用
常见的GC算法–标记清除(mark-Sweep)
- 该算法的核心思想就是 可达性
- 这个算法会设置一个 根对象,GC会定期从这个根开始,找到所有从根开始有 引用的对象,并做上标记,对于没有标记的对象,就认为是不可用的对象
V8的实现GC的算法
-
js引擎采用比较广泛的算法就是 标记清除算法,但是V8引擎对此又进行了更好的优化
-
标记整理算法(Mark-Compact)
- 与标记清除不同的是,对垃圾进行回收期间,会将没有被 清理的对象,进行 内存整理,整合空闲的空间,避免内存的碎片化
-
分代收集–对象被分成两组:新的和旧的
- V8引擎中,对象创建的时候,都是新的,会在不断的回收中慢慢的销毁
- 那些长期没有被销毁的,则会变成旧的,而且检查的频次也会随之减少
-
增量收集
- 如果当前内存中有很多对象,如果试图一次遍历并且标记,需要耗费一些事件,会带来明显的延迟
- 所以V8引擎试图,将垃圾收集工作分成几部分来做, 然后将这 几部分逐一进行处理,这样就会有很多微小的延迟,而不是很大的延迟
-
闲时收集
- GC 只会在CPU空闲的时候尝试运行,为了减少对代码的影响
JavaScript中的闭包
闭包的定义
-
在计算机科学中对闭包的定义
- 闭包:又称为词法闭包,或者函数闭包
- 是在支持 头等函数的编程语言中,实现词法绑定的一种技术
- 闭包在实现上是一个 结构体,它存储了 一个函数和一个关联的环境
-
闭包的概念出现于60年代,最早实现闭包的程序是Scheme,而JS中有大量的设计是来源于Scheme的
-
MDN对JS闭包的解释
- 一个函数和对其周围状态的引用捆绑在一起,这样的组合就是闭包
- 也就是说闭包可以让我们在一个内层函数中,访问到外层函数的作用域(JS通过作用域链实现的)
- 在JS中,每当创建一个函数,闭包就会在函数创建的同时被创建出来
-
个人理解
- 一个普通的function函数,如果它可以访问外层作用域的自由变量,那么这个函数和周围环境就是一个闭包
- 从不严格的角度来说:JS中的所有函数都是闭包
- 从严格的角度来说:JS中的一个函数,如果访问了外层作用域的变量,那么就是一个闭包
闭包的访问过程
-
以下代码的内存过程(大概)
- scope chain的指向是指向VO(GO、AO的)
- 内存的指向会一直存在,随着代码量的增多,占用的内存会越来越大
JavaScript内存泄漏以及释放内存
- 内存泄漏:对于那些永远不会再使用的对象,但是对于GC来说它不清楚,因此内存占用就会一直存在
- 通过垃圾回收机制,我们可以知道,若标记的对象一直存在,那么GC就不会清除该对象
- 我们只需要将adder5设置为null,那么adder5对应的对象,不可达了,所以GC就会将该内存回收
浏览器的优化操作
function foo(){
let name = "zhangcheng";
let age = 18;
function fooin(){
console.log(name)
}
}
- 在fooin中没有用到age,因此浏览器会做出相应的优化,在fooin闭包中不会有age的出现