JavaScript闭包概念和应用详解

JavaScript闭包概念和应用详解

什么是闭包?

闭包是指能够访问外部函数作用域中的变量的函数。闭包常常被用于隐藏对象的某些属性,或者封装一些数据,只提供访问接口。

闭包的形成条件

必须满足下面三个条件才可以形成闭包。

函数嵌套

一个函数A内部嵌套另外一个函数B,函数B可以访问函数A的变量,函数A也可以返回函数B,这样函数B就形成了一个闭包。

javascript 复制代码
function a() {
  var outerVar = 'I am outer';
  function b() {
    console.log(outerVar);
  }
  return b;
}

var closure = a();
closure(); // 输出:I am outer

注意 不是所有嵌套函数都会形成闭包,只有内部函数引用了外部函数的变量时,才会形成闭包。

下面的嵌套函数就没有形成闭包。

scss 复制代码
 function outer() {
  function inner() {
    console.log("Hello"); // 未引用外部变量
  }
  return inner;
}
const func = outer();
func(); // 输出 "Hello"

此时 inner 未引用 outer 的变量,不会形成闭包。

函数嵌套是结构基础,但闭包的核心在于 变量引用。

内部函数引用外部变量

内部函数引用外部变量触发闭包保留外部变量,避免被垃圾回收。

javascript 复制代码
function outer() {
  let count = 0;
  function inner() {
    count++; // 引用外部变量
  }
  return inner;
}
const increment = outer();
increment(); // count 被保留

inner函数引用了outer函数的变量count,当outer执行完毕之后,count不会被销毁,因为闭包(inner)仍然保留对它的引用。 若 inner 不引用 count,则 outer 执行完毕后,count 会被回收。

内部函数被暴露

内部函数被返回或者作为参数传递到其他作用域中执行,闭包就能持续存在。

javascript 复制代码
// 情况1:内部函数未被暴露
function outer() {
  let count = 0;
  function inner() { count++; }
  inner(); // 内部函数未被返回或传递
}
outer(); // outer 执行后,count 被销毁

// 情况2:内部函数被暴露
function outer() {
  let count = 0;
  function inner() { count++; }
  return inner; // 暴露到外部作用域
}
const func = outer();
func(); // count 被保留

暴露方式 不限于返回,还包括作为回调传递(如 setTimeout(inner, 100))。

闭包的核心作用

保留变量的状态

闭包可以使得变量生命周期延长,不会被垃圾回收机制回收。

实现私有化变量

通过闭包封装数据,避免全局污染。

函数柯里化

通过闭包保存参数,分步传递。

闭包的常见应用场景

在模块化开发中封装私有变量和方法

javascript 复制代码
const counter = (function() {
  let privateCount = 0;
  return {
    increment() { privateCount++; },
    getValue() { return privateCount; }
  };
})();

事件处理与回调

闭包可以使得事件处理函数访问到创建它的上下文中的变量。

javascript 复制代码
function setupButton(buttonId) {
  let count = 0;
  document.getElementById(buttonId).onclick = function() {
    count++;
    console.log(`点击次数:${count}`);
  };
}

节流和防抖

利用闭包保存定时器状态。 节流和防抖的详解文章(《10分钟彻底搞懂防抖和节流!前端性能优化的核心技巧,面试必问!》前端开发中,浏览器高频事件(如resize、scrol - 掘金)

闭包注意事项

内存泄漏

闭包可能导致变量无法释放,需要在不需要时手动解除引用)(如设置为null)。

javascript 复制代码
// 假设我们有一个按钮元素
const button = document.getElementById('myButton');

// 我们为这个按钮设置一个点击处理函数,该函数是一个闭包
button.onclick = (function() {
  let count = 0;
  return function() {
    count++;
    console.log(`Button clicked ${count} times.`);
  };
})();

// 现在假设我们不再需要这个按钮,我们将其从DOM中移除
document.body.removeChild(button);

// 但是,由于我们的闭包仍然引用着这个按钮,它不会被垃圾回收
// 为了避免内存泄漏,我们需要手动解除闭包对按钮的引用
button.onclick = null;

闭包会使得变量一直保存在内存中,如果闭包使用不当,可能会导致内存泄漏。因此,在使用闭包时,需要注意及时释放不再使用的变量。

循环中的闭包陷阱

javascript 复制代码
// 错误示例:所有定时器输出最终值 5
for (var i = 0; i < 5; i++) {
  setTimeout(() => console.log(i), 100);
}

// 解决方法:使用 let 或 IIFE 创建新作用域
for (let i = 0; i < 5; i++) {
  setTimeout(() => console.log(i), 100);
}

总结

闭包是指一个函数能够访问并记住外部作用域中的变量,简单来说就是在函数A中嵌套了函数B,函数B可以访问函数A中的变量,函数A也可以返回函数B,这样函数B就形成了一个闭包。实际开发中,闭包常常被用于保留变量状态和封装变量,举个例子,比如实现计数器、缓存数据或者像节流防抖这种控制高频操作的场景会使用闭包保存变量状态。也可以用来封装变量,将数据私有化避免全局污染。React Hooks的useSate就是通过闭包保留状态的。使用闭包需要注意处理不当可能会导致内存泄漏,比如闭包中引用了DOM元素,如果元素被移除但是闭包没有释放,内存就无法回收,这时候需要手动解除引用,比如设置为null。

相关推荐
我是一颗柠檬1 小时前
【MySQL全面教学】MySQL面试高频考点汇总Day15(2026年)
数据库·后端·mysql·面试
swipe3 小时前
DeepAgents 实战:用多 Agent 架构搭一个深度调研助手
javascript·面试·llm
雪宫街道3 小时前
synchronized 锁的范围:对象锁、类锁与代码块锁
java·jvm·后端·面试
云水一下4 小时前
JavaScript 从零基础到精通系列:前世今生与编程启蒙
前端·javascript
月亮邮递员6164 小时前
Markdown语法总结
开发语言·前端·javascript
AI人工智能+电脑小能手6 小时前
【大白话说Java面试题 第84题】【Mysql篇】第14题:为什么用 InnoDB 存储引擎的表建议用整型的自增主键?
java·开发语言·数据库·mysql·面试
小江的记录本6 小时前
【JVM虚拟机】JVM调优:常用JVM参数、调优核心指标、OOM排查、GC日志分析、Arthas工具使用(附《思维导图》+《面试高频考点清单》)
java·jvm·spring boot·后端·python·spring·面试
丷丩6 小时前
MapLibre GL JS第27课:添加COG栅格源
javascript·map·mapbox·maplibre gl js
不好听6137 小时前
JavaScript 到底是怎么运行的?从编译阶段到执行上下文全面解析
javascript
丷丩8 小时前
MapLibre GL JS第29课:添加Canvas源
javascript·gis·map·mapbox·maplibre gl js