上一节我们深入学习了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. 存储状态:闭包可以存储函数内部的状态,在多次调用中保持状态的连续性,常见的应用场景包括计数器、缓存等。
需要注意的是,闭包会引用外部函数中的变量,导致这些变量无法被垃圾回收。因此,在使用闭包时,需要注意内存管理,避免引起不必要的内存泄漏。
总结:本节通过嵌套函数的形式生动形象地描述了闭包的基本情况,让读者对闭包有一个基本的理解。
C语言网提供由在职研发工程师或ACM蓝桥杯竞赛优秀选手录制的视频教程,并配有习题和答疑,点击了解:
一点编程也不会写的:零基础C语言学练课程
解决困扰你多年的C语言疑难杂症特性的C语言进阶课程
从零到写出一个爬虫的Python编程课程
只会语法写不出代码?手把手带你写100个编程真题的编程百练课程
信息学奥赛或C++选手的 必学C++课程
蓝桥杯ACM、信息学奥赛的必学课程:算法竞赛课入门课程
手把手讲解近五年真题的蓝桥杯辅导课程