上一节我们深入学习了JS中函数返回值的相关知识,本节我们将会学习JS中闭包的概念。闭包是JS中的一大难点也是一大特色,在许多应用实现都是依靠闭包实现的。那什么是JS中的闭包呢?简言之:闭包是指一个函数能够记住并访问其词法作用域中的变量,即使该函数在其作用域之外执行。

首先我们先理解什么是词法作用域:

词法作用域(也称为静态作用域)是指变量的作用域在代码书写阶段就已经确定,而不是在代码执行时确定。

 /* 词法作用域演示 */
let a = 0; // 全局作用域,无论在哪里都可以访问这个变量
function f() {
  let b = 0; // 函数作用域,只能在函数内部访问
  function ff() {
    let c = 0; // 局部作用域,只能在ff函数内部访问
  }
  console.log(c); // 报错!不能访问内部函数的c变量
}

if (true) {
  let d = 0; // 块级作用域,只能在if块内部访问
}

console.log(b); // 报错!这里不能访问函数作用域的b变量
console.log(d); // 报错!这里不能访问块级作用域的d变量

其次,怎么让函数记住并能够访问词法作用域中的变量?

闭包最直观的形式就是函数嵌套了:

function f(){
  let cnt = 0;  // 1. 创建内部变量
  
  return function ff(){//2. 返回内部函数
    cnt++;  // 3. 内部函数访问外部变量
    console.log(cnt);
    return cnt;
  };
}
const F = f();  // 4. f()函数已经执行完毕,使得变量cnt被记住,与闭包同生命周期
/*cnt变量一直被记住!*/
F();  // 计数器: 1
F();  // 计数器: 2 
F();  // 计数器: 3
F();  // 计数器: 4

分析:当 f() 执行时,虽然函数本身执行完毕,但其内部函数 ff 对外部变量 cnt 的引用阻止了垃圾回收机制销毁 cnt(这里是阻止而不是让垃圾回收机制失效),从而形成了一个包含 ff 函数和对 cnt 变量引用的闭包实体。每次调用 F() 时,闭包通过维护的作用域链访问并修改被"捕获"的 cnt 变量,实现了跨作用域的状态持久化,这就是闭包的"记忆"效应——函数不仅记住了代码逻辑,更记住了执行环境中的变量状态。

闭包有以下特点:

1. 内部函数可以访问外部函数中的变量:闭包形成了一个作用域链,内部函数可以访问其外部函数中声明的变量和参数,包括外部函数执行完毕后仍然存在的变量。

2. 外部函数的变量不会被销毁:由于闭包中仍然引用了外部函数的变量,这些变量不会被垃圾回收机制回收,而是一直保留在内存中,直到闭包被销毁。

闭包的使用场景包括但不限于以下情况:

1. 封装私有变量:通过闭包可以创建私有变量,仅在闭包内部可访问,从而实现信息隐藏和封装。

2. 延迟执行:通过使用闭包,可以延迟函数的执行,例如在定时器中传递一个闭包函数来实现延迟执行某段代码。

3. 存储状态:闭包可以存储函数内部的状态,在多次调用中保持状态的连续性,常见的应用场景包括计数器、缓存等。

需要注意的是,闭包会引用外部函数中的变量,导致这些变量无法被垃圾回收。因此,在使用闭包时,需要注意内存管理,避免引起不必要的内存泄漏

 总结:本节通过嵌套函数的形式生动形象地描述了闭包的基本情况,让读者对闭包有一个基本的理解。

点赞(224)

C语言网提供由在职研发工程师或ACM蓝桥杯竞赛优秀选手录制的视频教程,并配有习题和答疑,点击了解:

一点编程也不会写的:零基础C语言学练课程

解决困扰你多年的C语言疑难杂症特性的C语言进阶课程

从零到写出一个爬虫的Python编程课程

只会语法写不出代码?手把手带你写100个编程真题的编程百练课程

信息学奥赛或C++选手的 必学C++课程

蓝桥杯ACM、信息学奥赛的必学课程:算法竞赛课入门课程

手把手讲解近五年真题的蓝桥杯辅导课程

Dotcpp在线编译      (登录可减少运行等待时间)