什么是闭包?
闭包指的是那些引用了另一个函数作用域中变量的函数,通常是在嵌套函数中实现
理解作用域链创建和使用的细节对理解闭包非常重要
执行上下文与作用域
任何变量(不管包含是原始值还是引用值)都存在于某个执行上下文中(也称为作用域)。这个上下文(作用域)决定了变量的生命周期,以及它们可以访问代码的哪个部分
- 执行上下文:
- 全局上下文: 在浏览器中指的是window对象,所有通过var定义的全局变量和函数都会成为window对象的属性和方法,使用let和const的顶级声明不会定义在全局上下文,但是在作用域链解析效果上是一样的。(全局上下文会在应用程序退出前销毁,比如关闭网页或者退出浏览器)
- 函数上下文: 函数在定义的时候,就会有一个作用域链( [scope] ), 这个作用域链决定了各级上下文中的代码在访问变量或者函数时的顺序。
- 块级上下文: 同函数上下文。
规则: 内部上下文可以通过作用域链访问外部上下文中的一切,但外部的上下文无法访问内部上下文中的任何东西,上下文之间连接是线性的、有序的。 每个上下文都可以到上一级上下文去搜索变量或者函数,但任何上下文都不能到下一级上下文中去搜索。
注意: 函数参数被认为是当前上下文中的变量,因此也跟其他变量遵循相同的规则。
- 函数内部的代码在访问变量时,就会使用给定的名称从作用域链中查找变量。函数执行完毕后,局部活动对象(函数上下文)会被销毁,内存中就会只剩下全局作用域(垃圾回收机制)。不过,闭包不一样。
垃圾回收机制
在C和C++语言等语言中,跟踪内存使用对开发者来说是个很大的负担,也是很多问题的来源。JavaScript为开发者卸下了这个负担,通过自动内存管理实现内存分配和闲置资源回收。
基本思路: 确定哪个变量不会再使用,然后释放它占用的内存。这个过程是周期性的,垃圾回收程序每隔一定时间就会自动运行。
在浏览器的发展史上,用到过两种主要的标记策略:
-
标记清理( make and sweep )
最常用的垃圾回收策略就是标记清理。
比如在函数内部声明一个变量时,这个变量就会被标记(存在上下文中),当变量离开上下文时,就会被标记为离开上下文。随后垃圾回收程序做一次内存清理,销毁带标记的所有值并释放他们的内存 -
引用计数( reference counting )
需要记录值被引用了多少次。JavaScript不再使用这种算法,某些旧版本的IE会有影响,原因是JavaScript会访问非原生JavaScript对象(DOM元素)
这种算法存在一个严重的问题, 循环引用, 也就会造成内存泄漏
闭包作用
- 保护私有变量,防止全局变量污染(可以访问私有变量)
- 闭包的作用域链中包含自己的变量,还有函数的变量到全局上下文。
- 通常函数作用域及其中的所有变量调用完成之后都会被销毁,但是闭包不会,闭包在被函数返回之后,作用域会一直保存在内存中,直到闭包被销毁。
闭包的影响
注意: 使用闭包和私有变量会导致作用域链变长,作用域链变长会导致查找变量的时间也会变多
解决办法
解除对函数的引用,就能释放内存。
function add () {
let a = 1;
return function () {
a++;
return a;
}
}
let result = add();
result(); // 2
result(); // 3
// 解除对函数的引用,就能释放内存(就会被垃圾回收机制回收)
add = null;