闭包
当函数
可以访问定义时的外层作用域
时,就产生了闭包
闭包是在定义时产生的,与在哪调用无关
广义上讲,所有js函数都是闭包
function bar(age) {
function baz() {
console.log(age)
}
return baz
}
var func = bar(18)
func()
在这个例子中我们将baz
的函数对象返回,并使用func
来接收这个对象
调用func
其实就是调用baz
在bar
调用完毕后,垃圾回收机制
通常会将这段内存回收
,但闭包
的神奇之处在于他会阻止这件事发生
因为baz
此时正在使用bar的作用域
baz始终保持着对bar的引用,这个引用就叫做闭包
内存泄漏
正如上面所写的,闭包
会导致js的垃圾回收机制
无法回收
对应的内存
这虽然方便了获取外层作用域的变量
,但同样也会造成内存泄漏
要想知道为什么会造成内存泄漏
,就要知道js中的垃圾回收机制
是怎么工作的
内存管理
无论是什么编程语言
,在代码的执行过程中都需要对内存
进行操作,如分配内存
,释放内存
等等
这个功能被称之为内存管理
具体而言,内存具有以下几个生命周期
- 申请内存
- 使用内存
- 释放内存
不同的编程语言对于内存的管理是有区别的
大体可分为自动
与手动
- 手动管理内存
最常见的就是c语言
,通过malloc
和free
来动态申请内存和释放内存 - 自动管理内存
如java
和JavaScript
,他们会自动
帮助我们申请内存释放内存
具体到JavaScript
中,JavaScript
会在我们定义数据时分配内存
对于原始数据
,JavaScript会直接在栈内存中分配内存
对于复杂数据
,JavaScript会在堆内存中开辟一块空间用于存放并返回一个指针给变量
当内存
使用完毕后就需要对其进行释放
以便腾出更多的内存
这个步骤就叫做垃圾回收
(Garbage Collection,简称GC
)
垃圾回收机制
对于那些不再需要的对象
,我们称之为垃圾
,需要对其进行回收
,以腾出更多的内存
具体而言,垃圾回收机制
通过算法
来实现
常见GC算法
以下列举了一些常见的GC算法
引用计数
每当有一个对象能指向自己
时,自己的引用数
便+1
当引用数
为0
时就销毁
这个对象
然而当产生循环引用
时这个算法就无法处理
循环引用即对象a引用对象b,对象b引用对象a
标记清除
标记清除
的核心就是可达性
,如果无法达到
就清除
算法会设置一个根对象
,垃圾回收器
会定期从根对象
开始寻找所有引用到的对象
对于不可达对象
,就会对其回收
这个算法可以很好解决循环引用
问题
V8引擎的优化
在V8引擎
中广泛采用的是标记可达算法
同时为了对其进行更好的优化,在算法具体实现上也结合了其他一些算法
- 标记整理
和标记清除
相似,不同的是,回收期间同时会将保留的存储对象搬运汇集到连续的内存空间
,从而整合空闲空间
,避免内存碎片化
- 分代收集
对象会被分成两组:“新的
”和“旧的
”
新的对象在很快的时间内
完成工作就会被清理
如果对象长期存活
就会变得“老旧
”,被检查的频次也会减少
- 增量收集
如果有许多对象
,要一次遍历完的话会消耗很多时间,占用很多性能,并且会在操作中带来很大延迟
所以引擎会试图将一次遍历操作分成几个部分
来完成,这样会有许多微小的延迟而不是一个大的延迟
- 闲时收集
垃圾回收器
只会在CPU
空闲时尝试运行,以减少可能对代码执行的影响
关于闭包导致的内存泄漏
再次说回关于闭包所引起的内存泄漏
问题
我们来看这样一段代码
function bar(age) {
function baz() {
console.log(age)
}
return baz
}
var func = bar(18)
func()
注意这里的baz
函数形成了闭包
这是这段代码运行到最后一行的内存状态
在第7行
代码运行完后,我们期望bar
对应的AO
回收掉,但GC
根据可达性
原则,发现能从根对象
寻找到bar
的AO
对象,所以就不对其进行回收
关于AO,GO等等可以看我这一篇文章
js执行原理
即如下图
我们能从GO
中找到func
对象,能从func
的scopes
中寻找到bar
对象,那么GC
就无法对bar
对象进行回收
解决方法也很简单,在确认对象使用完毕,以后不会再使用
时,手动释放
不用的内存,即将func
赋为null
function bar(age) {
function baz() {
console.log(age)
}
return baz
}
var func = bar(18)
func()
func=null
当从GO
对象中无法寻找到baz
对象时,那么GC
就会回收
这些内存
这一切的前提都是这些对象确认不必再使用时才进行的
浏览器对闭包的优化
当闭包中存在着没有被使用
的属性时,浏览器会将其销毁
,以节省空间
例如以下代码
function bar() {
var name = "bar"
var age = 18
function baz() {
console.log(age)
}
return baz
}
var func = bar()
func()
我们可以看到当运行baz
函数时,它的作用域链
中并没有name
属性
在控制台
中也获取不到name