小白也能学会 闭包

一、什么是闭包?

闭包 = 函数 + 它能访问的外部变量

换句话说:

闭包就是一个函数能够"记住"并访问它定义时所在的环境中的变量。

听起来有点抽象?没关系,我们先从一个简单的例子开始。


示例 1:一个最简单的闭包

js 复制代码
function outer() {
  const name = "小明";

  function inner() {
    console.log(name); // 使用了外部函数 outer 的变量 name
  }

  return inner;
}

const sayName = outer(); // 这里返回的是 inner 函数本身
sayName(); // 输出 "小明"

分析这个过程:

  1. outer() 函数执行后返回了内部的 inner 函数。
  2. 外部变量 name 是在 outer() 内部定义的。
  3. 即使 outer() 执行完了,它的局部变量 name 并没有被销毁。
  4. sayName() 实际上是 inner() 函数,它仍然能访问到 name 变量。

这就是闭包的核心特性:即使外层函数执行完毕,其内部变量依然可以被内部函数访问和保留。


二、闭包的本质:函数+作用域链

闭包不是一种语法结构,而是一种行为模式。它的本质是:

当一个函数能够访问并记住其词法作用域(也就是它定义时所处的作用域),即使该函数在其作用域外执行,也形成了闭包。


示例 2:闭包让变量"私有化"

js 复制代码
function counter() {
  let count = 0;

  return function () {
    count++;
    console.log(count);
  };
}

const increment = counter();

increment(); // 输出 1
increment(); // 输出 2
increment(); // 输出 3

解释:

  • counter() 返回了一个函数,这个函数每次调用都会增加 count
  • countcounter() 中的局部变量,正常情况下函数执行完后应该被销毁。
  • 但由于返回的函数"记住了"这个变量,所以它不会被销毁,而是继续存在内存中。
  • 我们通过闭包实现了对 count 的保护和控制 ------ 这就是 JavaScript 中实现"私有变量"的一种方式!

三、闭包的常见应用场景

闭包非常强大,它在很多地方都有广泛的应用,下面是一些最常见的用途:


数据封装(私有变量)

就像上面的例子一样,我们可以创建一些只能通过特定方法访问的变量。

js 复制代码
function createCounter() {
  let count = 0;

  return {
    increment: function () {
      count++;
    },
    getCount: function () {
      return count;
    }
  };
}

const counter = createCounter();
counter.increment();
console.log(counter.getCount()); // 输出 1
// count 无法直接访问或修改

优点:

  • 避免全局变量污染
  • 提高数据安全性

柯里化(Currying)与函数工厂

闭包可以用来生成定制化的函数。

js 复制代码
function add(x) {
  return function (y) {
    return x + y;
  };
}

const add5 = add(5);
console.log(add5(3)); // 输出 8
console.log(add5(10)); // 输出 15

这里 add(5) 返回了一个新函数,它"记住"了 x=5,这就是闭包的力量。


回调函数中保存状态

闭包常用于事件处理、异步编程中保存上下文。

js 复制代码
function setupTimer(name) {
  setTimeout(function () {
    console.log("你好," + name);
  }, 1000);
}

setupTimer("张三"); // 1秒后输出 "你好,张三"

在这个例子中,匿名回调函数访问了 setupTimer 中的 name,这其实也是闭包。


循环中绑定事件(经典问题)

这是初学者最容易踩坑的地方:

html 复制代码
<ul>
  <li>项目1</li>
  <li>项目2</li>
  <li>项目3</li>
</ul>
js 复制代码
const items = document.querySelectorAll("li");

for (var i = 0; i < items.length; i++) {
  items[i].addEventListener("click", function () {
    alert("你点击了第 " + i + " 个项目");
  });
}

你会发现无论点击哪个 <li>,弹出的都是"第 3 个项目"。

为什么会这样?

因为 var 是函数作用域,循环结束后 i 的值已经是 3。所有回调函数共享同一个 i

使用闭包解决这个问题:

js 复制代码
for (var i = 0; i < items.length; i++) {
  (function (index) {
    items[i].addEventListener("click", function () {
      alert("你点击了第 " + index + " 个项目");
    });
  })(i);
}

或者更简单的方式(推荐):

js 复制代码
for (let i = 0; i < items.length; i++) {
  items[i].addEventListener("click", function () {
    alert("你点击了第 " + i + " 个项目");
  });
}

let 是块级作用域,每个循环都会创建一个新的 i,相当于自动形成了闭包。


四、闭包的缺点

虽然闭包很强大,但也有一些需要注意的问题:

缺点 说明
内存占用高 闭包会保留外部函数的变量在内存中,可能导致内存泄漏
性能影响 如果滥用闭包,可能会影响程序性能
调试困难 闭包嵌套多层时,调试起来可能比较复杂

五、一句话总结闭包

闭包是一个函数能够访问并记住它定义时所处的作用域中的变量,即使该函数在该作用域之外执行。

也可以简化为:

闭包 = 函数 + 它能访问的外部变量


六、闭包练习题(动手试试看)

练习1:

js 复制代码
function outer() {
  var x = 10;
  return function inner(y) {
    return x + y;
  };
}

var result = outer();
console.log(result(5)); // 输出多少?

练习2:

js 复制代码
function createMultiplier(multiplier) {
  return function (num) {
    return num * multiplier;
  };
}

const double = createMultiplier(2);
console.log(double(5)); // 输出多少?

练习3(经典面试题):

js 复制代码
for (var i = 1; i <= 3; i++) {
  setTimeout(function () {
    console.log(i);
  }, 1000);
}

这段代码会输出什么?如何修改让它依次输出 1、2、3?


七、闭包 vs 其他概念对比

概念 特点
闭包 函数 + 外部变量,保持变量不被销毁
作用域 控制变量的可访问范围
this 动态绑定,取决于函数调用方式
箭头函数 没有自己的 this,继承外层 this
模块模式 利用闭包实现模块封装、暴露接口

八、结语

闭包是 JavaScript 中非常核心、也非常强大的概念。虽然刚开始可能会觉得有点绕,但只要你理解了它的原理和使用场景,就能写出更优雅、更高效的代码。

闭包就像是你的函数随身带着一个小背包,里面装着它出生时周围的变量。

如果你现在看完这篇文章,已经能回答这些问题:

  • 什么是闭包?
  • 为什么需要闭包?
  • 闭包有什么好处和坏处?
  • 常见的闭包写法有哪些?
  • 如何避免闭包带来的问题?

那么恭喜你,你已经掌握了 JavaScript 中最重要的概念之一!

相关推荐
lpfasd12312 分钟前
从 Electron 转向 Tauri:用 Rust 打造更轻、更快的桌面应用
javascript·rust·electron
非凡ghost30 分钟前
图吧工具箱-电脑硬件圈的“瑞士军刀”
前端·javascript·后端
非凡ghost31 分钟前
Xrecode3(多功能音频转换工具)
前端·javascript·后端
橙某人32 分钟前
飞书多维表格插件:进一步封装,提升开发效率!🚀
前端·javascript
非凡ghost44 分钟前
Subtitle Edit(字幕编辑软件) 中文绿色版
前端·javascript·后端
用户84298142418101 小时前
10个JavaScript编程实用技巧
javascript
扎瓦斯柯瑞迫1 小时前
cursor: 10分钟魔改环境、优雅获取Token
前端·javascript·后端
Violet_YSWY1 小时前
将axios、async、Promise联系在一起讲一下&讲一下.then 与其关系
开发语言·前端·javascript
San301 小时前
扩展卡片效果:用 Flexbox 和 CSS 过渡打造惊艳交互体验
前端·javascript·css
写代码的皮筏艇1 小时前
JS数据类型转换
前端·javascript