当前位置: 首页 > news >正文

JavaScript 闭包(Closure)深度讲解

1.什么是闭包:

闭包是JavaScript中最强大且独特的特性之一,它是函数与其词法环境的组合。闭包使得函数能够访问其外部作用域的变量,即使外部函数已经执行完毕。这种机制让JavaScript具备了许多其他语言需要复杂语法才能实现的功能。

// 最简单的闭包示例
function outer() {const message = "Hello from outer!"; // 外部变量function inner() {console.log(message); // inner函数"记住"了message}return inner; // 返回inner函数
}const myClosure = outer(); 
myClosure(); // "Hello from outer!"

2. 核心概念与前置知识:

2.1 执行上下文 (Execution Context):

执行上下文是 JavaScript 代码执行时的环境,每当代码执行时都会创建对应的执行上下文。

执行上下文栈 (Call Stack)
全局执行上下文
Global EC
函数执行上下文 1
Function EC 1
函数执行上下文 2
Function EC 2
函数执行上下文 3
Function EC 3
执行上下文的组成部分:
// 伪代码:执行上下文的内部结构
ExecutionContext = {// 1. 词法环境 (用于let/const和函数声明)LexicalEnvironment: {EnvironmentRecord: {}, // 环境记录outer: null            // 外部环境引用},// 2. 变量环境 (用于var声明)VariableEnvironment: {EnvironmentRecord: {},outer: null},// 3. this绑定ThisBinding: undefined
}

2.2 词法环境 (Lexical Environment):

词法环境是存储标识符-变量映射的结构,由环境记录和外部环境引用组成。

环境记录类型
词法环境结构
声明式环境记录
Declarative
对象环境记录
Object
全局环境记录
Global
词法环境
Lexical Environment
环境记录
Environment Record
外部环境引用
Outer Reference
词法环境的创建过程:
function createLexicalEnvironment() {// 示例:词法环境的创建function outer(x) {let a = 10;const b = 20;function inner(y) {let c = 30;console.log(a + b + c + x + y); // 访问多个作用域的变量}return inner;}return outer;
}// 执行过程中的词法环境变化
/*
1. 全局词法环境:
{EnvironmentRecord: {createLexicalEnvironment: <function>,outer: <function>},outer: null
}2. outer函数的词法环境:
{EnvironmentRecord: {x: 参数值,a: 10,b: 20,inner: <function>},outer: <全局词法环境的引用>
}3. inner函数的词法环境:
{EnvironmentRecord: {y: 参数值,c: 30},outer: <outer函数词法环境的引用>
}
*/

2.3 作用域链 (Scope Chain)

作用域链是通过词法环境的外部引用形成的链条,用于标识符解析。

当前词法环境
父级词法环境
全局词法环境
null
作用域链查找算法
// 作用域链查找示例
function demonstrateScopeChain() {const globalVar = "全局变量";function level1() {const level1Var = "第一层变量";function level2() {const level2Var = "第二层变量";function level3() {const level3Var = "第三层变量";// 变量查找顺序演示console.log(level3Var); // 1. 在当前环境找到console.log(level2Var); // 2. 向上一层查找console.log(level1Var); // 3. 继续向上查找console.log(globalVar);  // 4. 查找到全局环境// console.log(nonExistent); // 5. 找不到则报错}return level3;}return level2;}return level1;
}// 调用过程中的作用域链
const fn = demonstrateScopeChain()()();
fn(); // 执行时会沿着作用域链查找变量

3. 代码示例与分析:

3.1 经典的闭包示例

function makeCounter() {let count = 0; // 外部函数的局部变量return function() { // 返回的内部函数形成闭包count++; // 访问外部函数的变量return count;};
}const counter = makeCounter(); // 调用外部函数
console.log(counter()); // 1 - 调用闭包函数
console.log(counter()); // 2 - count 变量被保持
console.log(counter()); // 3

3.2 逐行执行过程分析

全局执行上下文makeCounter执行上下文闭包函数1. 创建全局执行上下文2. 调用makeCounter(),创建新执行上下文3. 创建count变量,值为04. 创建匿名函数,形成闭包5. 返回函数,makeCounter执行完毕6. makeCounter执行上下文出栈7. 但闭包仍保持对count的引用8. 调用counter()9. 访问并修改count变量10. 返回结果全局执行上下文makeCounter执行上下文闭包函数

详细步骤解析:

// 步骤 1-2: 全局执行上下文创建,调用makeCounter
function makeCounter() {// 步骤 3: 在makeCounter的词法环境中创建count变量let count = 0;// 步骤 4: 创建匿名函数,该函数的[[Environment]]属性// 指向makeCounter的词法环境,形成闭包return function() {// 步骤 9: 通过作用域链找到外部的count变量count++;return count;};// 步骤 5-6: makeCounter执行完毕,执行上下文出栈// 但count变量因为被闭包引用而不会被垃圾回收
}// 步骤 7: counter变量保存了闭包函数的引用
const counter = makeCounter();// 步骤 8-10: 每次调用counter()都会访问保存的count变量
console.log(counter()); // 1

3.3 V8 引擎的优化

V8 引擎对闭包进行了以下优化:

  1. 变量提升优化:只保留被闭包实际使用的外部变量
  2. 内存管理:未被引用的外部变量会被垃圾回收
  3. 作用域分析:在编译时分析变量使用情况
function optimizationExample() {let used = "被闭包使用的变量";let unused = "未被使用的变量"; // V8会优化掉这个变量let alsoUnused = "同样未被使用";return function() {console.log(used); // 只有这个变量会被保留在闭包中};
}

注意:在调试时,由于V8的优化,某些未使用的变量可能在调试器中显示为 “undefined”。

4. 经典应用场景与最佳实践:

4.1 模块化封装

const Calculator = (function() {let result = 0; // 私有变量return {add: function(x) {result += x;return this;},multiply: function(x) {result *= x;return this;},getResult: function() {return result;},reset: function() {result = 0;return this;}};
})();// 使用
Calculator.add(5).multiply(2).getResult(); // 10

4.2 事件处理与回调

function createButtonHandler(name) {return function(event) {console.log(`按钮 ${name} 被点击了`);// name变量被闭包保存};
}document.getElementById('btn1').onclick = createButtonHandler('按钮1');
document.getElementById('btn2').onclick = createButtonHandler('按钮2');

4.3 防抖和节流

// 防抖函数
function debounce(func, delay) {let timeoutId; // 被闭包保存的变量return function(...args) {clearTimeout(timeoutId);timeoutId = setTimeout(() => {func.apply(this, args);}, delay);};
}// 节流函数
function throttle(func, delay) {let lastCall = 0;return function(...args) {const now = Date.now();if (now - lastCall >= delay) {lastCall = now;func.apply(this, args);}};
}

5. 常见陷阱与解决方案:

5.1 循环中的闭包陷阱

问题代码:

// 经典错误示例
for (var i = 0; i < 3; i++) {setTimeout(function() {console.log(i); // 输出三次 3}, 100);
}

解决方案:

// 方案1:使用IIFE创建新的作用域
for (var i = 0; i < 3; i++) {(function(j) {setTimeout(function() {console.log(j); // 输出 0, 1, 2}, 100);})(i);
}// 方案2:使用let块级作用域
for (let i = 0; i < 3; i++) {setTimeout(function() {console.log(i); // 输出 0, 1, 2}, 100);
}// 方案3:使用bind
for (var i = 0; i < 3; i++) {setTimeout(function(j) {console.log(j); // 输出 0, 1, 2}.bind(null, i), 100);
}

5.2 内存泄漏风险

// 可能导致内存泄漏的代码
function createHandler() {const largeData = new Array(1000000).fill('data'); // 大量数据return function() {// 即使不使用largeData,它也会被闭包保留console.log('handler called');};
}// 解决方案:显式释放不需要的引用
function createHandler() {const largeData = new Array(1000000).fill('data');const needed = largeData.slice(0, 10); // 只保留需要的部分return function() {console.log(needed.length);// largeData会被垃圾回收};
}
http://www.dtcms.com/a/393449.html

相关文章:

  • QT与Spring Boot通信:实现HTTP请求的完整指南
  • 服务器ubuntu 22.04装nvidia驱动
  • nginx流量复制
  • spring-ai-alibaba-nl2sql 学习(五)——python 分析
  • 分布式链路追踪关键指标实战:精准定位服务调用 “慢节点” 全指南(三)
  • SimpleVLA-RL:通过 RL 实现 VLA 训练的 Scaling
  • Java 大视界 -- 基于 Java 的大数据可视化在企业供应链动态监控与优化中的应用
  • 《Linux 进程控制完全指南》
  • GitHub 热榜项目 - 日榜(2025-09-21)
  • 鹿鼎记豪侠传:Rust 重塑 iOS 江湖(上)
  • echarts监听dataZoom拖动缩放事件
  • Chrome学习小记3:基于Chrome Views框架创建最小示例窗口A(从Example分析开始)
  • Chrome学习小记2:GN构建系统小记
  • Chrome性能优化指南大纲
  • 【iOS】AFNetworking学习
  • Kafka 分层存储(Tiered Storage)原理、配置、快速上手与生产落地
  • 多元函数微分学核心概念辨析:连续、偏导与可微
  • 9.21 快选|倍增|栈+贡献法
  • AI.工作助手.工作提效率.AI应用开发平台
  • 【名人简历】鲁迅
  • linux文件系统基本管理
  • 2.1 进程与线程 (答案见原书 P57)
  • SDL2 开发详解
  • c++ 深拷贝之 std::string 与 char*
  • [数理逻辑] 决定性公理与勒贝格可测性(II) 一维情况
  • [Tongyi] DeepResearch Model | MODEL_PATH
  • 儿童对话玩具模型设计与实现
  • 生成器迁移的偏差消除条件
  • LeetCode 刷题【86. 分隔链表】
  • 回溯.专题