小白也能学会 闭包

一、什么是闭包?

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

换句话说:

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

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


示例 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 中最重要的概念之一!

相关推荐
Mintopia1 小时前
像素的进化史诗:计算机图形学与屏幕的千年之恋
前端·javascript·计算机图形学
Mintopia1 小时前
Three.js 中三角形到四边形的顶点变换:一场几何的华丽变身
前端·javascript·three.js
归于尽2 小时前
async/await 从入门到精通,解锁异步编程的优雅密码
前端·javascript
晓13132 小时前
JavaScript加强篇——第六章 定时器(延时函数)与JS执行机制
开发语言·javascript·ecmascript
yanlele2 小时前
【实践篇】【01】我用做了一个插件, 点击复制, 获取当前文章为 Markdown 文档
前端·javascript·浏览器
LeeAt2 小时前
手把手教你构建自己的MCP服务器并把它连接到你的Cursor
javascript·cursor·mcp
前端风云志3 小时前
TypeScript枚举类型应用:前后端状态码映射的最简方案
javascript
望获linux3 小时前
【实时Linux实战系列】多核同步与锁相(Clock Sync)技术
linux·前端·javascript·chrome·操作系统·嵌入式软件·软件
小飞悟3 小时前
JavaScript 中的“伪私有”与“真私有”:你以为的私有变量真的安全吗?
javascript