普通视图

发现新文章,点击刷新页面。
昨天以前首页

JavaScript闭包概念和应用详解

2025年6月5日 22:44

JavaScript闭包概念和应用详解

什么是闭包?

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

闭包的形成条件

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

函数嵌套

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

function a() {
  var outerVar = 'I am outer';
  function b() {
    console.log(outerVar);
  }
  return b;
}

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

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

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

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

此时 inner 未引用 outer 的变量,不会形成闭包。
函数嵌套是结构基础,但闭包的核心在于 变量引用。

内部函数引用外部变量

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

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 会被回收。

内部函数被暴露

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

// 情况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))。

闭包的核心作用

保留变量的状态

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

实现私有化变量

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

函数柯里化

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

闭包的常见应用场景

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

const counter = (function() {
  let privateCount = 0;
  return {
    increment() { privateCount++; },
    getValue() { return privateCount; }
  };
})();

事件处理与回调

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

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

节流和防抖

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

闭包注意事项

内存泄漏

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

// 假设我们有一个按钮元素
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;

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

循环中的闭包陷阱

// 错误示例:所有定时器输出最终值 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。

❌
❌