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);

  1. 在浏览器中的表现:<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 />还剩下的那一半,如果不再被使用,也不被手动清除,那么就或造成内存泄漏,占据内存中的部分空间。
  2. <a name="um4cg"></a>
  3. ## 补充
  4. 案例:
  5. ```jsx
  6. function foo() {
  7. var name = "zx";
  8. var age = 18;
  9. function bar() {
  10. debugger;
  11. console.log(name);
  12. }
  13. return bar;
  14. }
  15. var fn = foo();
  16. fn();

虽然ECMA规范中说的是内层函数会保存父级的作用域,也就是说age也会被保留,但实际浏览器引擎在实现上为了性能是没有保留age的,可以通过断点调试查看closure来验证
image.png
闭包中只保存了name变量,所以我们在控制台打印name是可以的,但是打印age就会报错
image.png
除非我们在bar中对age变量也有使用,不然浏览器引擎会自动清除它。