JS中闭包的定义
这里先看一下闭包的定义,分成两个:在计算机科学中和在JS中。
在计算机科学中对闭包的定义(维基百科):
- 闭包(英语:Closure),又称 词法闭包(Lexical Closure)或 函数闭包(function closures)
- 是在支持 头等函数 的编程语言中,实现词法绑定的一种技术
- 头等函数解释:支持函数作为第一公民的编程语言
- 闭包在实现上是一个结构体,它存储了一个函数和一个关联的环境(相当于一个符号查找表)
- 闭包跟函数最大的区别在于,当捕捉闭包的时候,它的 自由变量 会在捕捉时被确定,这样即使脱离了捕捉时的上下文,它也能照常运行
闭包的概念出现于60年代,最早实现闭包的程序是Scheme,那么我们就可以理解为什么JS中有闭包:
- 因为JS中有大量的设计是源于Scheme的
MDN对于JS闭包的解释:
- 一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)
- 也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域
在JS中,每当创建一个函数,闭包就会在函数创建的同时被创建出来
JS中函数的执行过程
闭包的终极定义
闭包是由两部分组成的:
一个函数
- 这个函数可以访问的自由变量
- 自由变量:能够被跨作用域引用的变量
严格的闭包定义:
- 一个函数
- 函数必须访问自身作用域外部的变量
宽泛的闭包定义:
- 一个函数
- 函数能访问自身作用域外的变量
闭包的内存表现和内存泄漏
案例: ```jsx function createFnArray() { var arr = new Array(1024 * 1024).fill(1); return function () { console.log(arr.length); }; }
// var arrayFn = createFnArray();
var arrayFns = []; for (var i = 0; i < 100; i++) { setTimeout(() => { arrayFns.push(createFnArray()); }, i * 100); }
setTimeout(() => { for (var i = 0; i < 50; i++) { setTimeout(() => { arrayFns.pop(); }, 100 * i); } }, 10000);
在浏览器中的表现:<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/422931/1642380238909-d5e8b9cd-46b9-440f-94e1-ab1500b3ad6b.png#clientId=u97d7179d-e1d6-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=189&id=udfa2e450&margin=%5Bobject%20Object%5D&name=image.png&originHeight=189&originWidth=1125&originalType=binary&ratio=1&rotation=0&showTitle=false&size=11566&status=done&style=none&taskId=u1d56ae4a-787c-416a-8f76-1f2e569bb49&title=&width=1125)<br />可以看到,随着for循环的执行,arrayFns.push(createFnArray())不断被执行,每次执行都会开辟新的内存空间,所以蓝色线条是一直向上延伸的,直到for循环执行完毕<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/422931/1642380353671-637d512d-a6fd-4865-9187-5d7aeb23a08d.png#clientId=u97d7179d-e1d6-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=115&id=u2dde5f00&margin=%5Bobject%20Object%5D&name=image.png&originHeight=115&originWidth=1028&originalType=binary&ratio=1&rotation=0&showTitle=false&size=8237&status=done&style=none&taskId=ubb449567-e0a9-41c5-8105-e3c3ca7e7ec&title=&width=1028)<br />在第14到第15秒之间,执行了我们第二个setTimeout函数,这里清除了arrayFns数组一般的数据,所以内存占用降到了一半以下,这就是js函数执行在内存中的表现。<br />还剩下的那一半,如果不再被使用,也不被手动清除,那么就或造成内存泄漏,占据内存中的部分空间。
<a name="um4cg"></a>
## 补充
案例:
```jsx
function foo() {
var name = "zx";
var age = 18;
function bar() {
debugger;
console.log(name);
}
return bar;
}
var fn = foo();
fn();
虽然ECMA规范中说的是内层函数会保存父级的作用域,也就是说age也会被保留,但实际浏览器引擎在实现上为了性能是没有保留age的,可以通过断点调试查看closure来验证
闭包中只保存了name变量,所以我们在控制台打印name是可以的,但是打印age就会报错
除非我们在bar中对age变量也有使用,不然浏览器引擎会自动清除它。