正在加载...

闭包

你已经懂了作用域:从这里能看见哪些名字。再进一步:外层调用结束后,内层函数仍可继续使用外层名字。这种持久联系就是闭包(closure)。

闭包是什么意思

当函数在另一个函数内部被创建,并且之后仍能访问外层的绑定,就形成了闭包。外层调用可能已经结束;内层函数仍然「记得」那些名字。你不会在代码里写 closure 这个词——你写嵌套函数,JavaScript 会把需要的外层绑定保持存活。

盯住三块

📊 外层函数、绑定与内层函数

部分 作用 初学者提示
外层函数 运行一次(或多次),常常返回内层函数 「准备」那一次调用
外层绑定 外层里的参数或 let / const 内层仍然需要的值
内层函数 之后调用时使用该绑定 你要保留的那个函数

能指出这三块,就能解释几乎所有初学者闭包例子。惊喜不在嵌套本身——而在外层 return 之后,外层绑定仍然有用。

内层函数读取外层名字

javascript
function makeLine(title) {
function show() {
console.log(title);
}
show();
}
makeLine("Shop");

调用进行时,内层朝外查找

show 没有本地的 title。查找向外走到 makeLine,找到参数。这是普通的嵌套作用域。闭包再进一步:在 makeLine 返回之后,把 show(或它的孪生)留到以后用。

return 之后保留内层函数

javascript
function makeLine(title) {
return function show() {
console.log(title);
};
}
let printShop = makeLine("Shop");
printShop();

外层调用结束——记忆仍在

makeLine("Shop") 返回一个函数。之后调用 printShop,它仍打印 Shop。那次已结束调用里的参数 title 仍然可达。这种持久捕获就是闭包。

小型问候工厂

javascript
function makeGreeter(name) {
return function () {
return "Hello, " + name;
};
}
let hiSara = makeGreeter("Sara");
console.log(hiSara());

工厂返回现成函数

makeGreeter 是个小工厂:一次外层调用,返回一个定制函数。hiSara 总是问候 Sara,因为它闭合了那个 name。换掉传给 makeGreeter 的参数,就得到另一个问候者——同一模式,新的记忆。

会记住的计数器

javascript
function makeCounter() {
let n = 0;
return function () {
n = n + 1;
return n;
};
}
let tick = makeCounter();
console.log(tick());
console.log(tick());

用计数器做近似私有状态

n 住在外层函数里。外面的代码不能直接碰 n——只有返回的函数可以。第一次调用返回 1,第二次返回 2。没有全局变量,计数器也能「记住」。

两个计数器,两份记忆

javascript
function makeCounter() {
let n = 0;
return function () {
n = n + 1;
return n;
};
}
let a = makeCounter();
let b = makeCounter();
console.log(a());
console.log(a());
console.log(b());

每次外层调用各有记忆

a 和 b 来自两次不同的 makeCounter(),因此各自有自己的 n。对 a 踩两下,不会让 b 从中间开始。除非你故意共享一个外层绑定,闭包不会自动共用一袋状态。

把闭合的值显示在页面上

用工厂做一个问候者,调用一次,把返回的字符串放进段落。

带闭包的页面演示

html
<!DOCTYPE html>
<html>
<head>
<title>closures — page</title>
</head>
<body>
<p id="msg">…</p>
<script>
function makeGreeter(name) {
return function () {
return "Hello, " + name;
};
}
let hi = makeGreeter("Omar");
let box = document.getElementById("msg");
box.textContent = hi();
</script>
</body>
</html>

段落显示 Hello, Omar。外层调用已结束;hi 仍知道 name。只改传给 makeGreeter 的字符串,刷新,读新行。下一课:对象(objects)——把相关值收在一个名字下。

练习步骤

  1. 写一个返回内层函数的函数,内层日志记录外层参数。
  2. 把返回的函数存进变量,等外层调用结束后再调用它。
  3. 构建 makeGreeter("Lina") 并日志返回的字符串。
  4. 创建一个计数器;调用两次,确认先是 1 再是 2。
  5. 创建两个计数器;只给第一个加两下,确认第二个仍从 1 开始。
  6. 在 HTML 演示里只改名字参数,刷新,读段落。

常见踩坑

  • 以为外层局部变量在 return 瞬间消失——内层仍能看见
  • 以为每次 makeCounter() 都共享同一个 n——通常每次调用各有记忆
  • 以为「闭包」只等于关键字 return——return 可以交回内层,记忆是被闭合的绑定
  • 为了「简单」把每个要记住的值都放到全局对象上
  • 在这里期待完整的对象课——把属性收在一个值下是下一页

小结

  • 闭包让内层函数在外层调用结束后仍能使用外层名字
  • 你写嵌套函数;JavaScript 保持所需绑定存活
  • 工厂返回记住准备值的定制函数
  • 计数器把私有 n 藏在返回函数后面
  • 分开的外层调用通常意味着分开的记忆
  • 页面更新可以显示由闭合名字产生的字符串
  • 下一课:对象——相关值收在一个名字下

🧠 测试您的知识

准备开始

测试您的知识

通过这个互动测验挑战自己,看看你对这个主题的理解程度如何

❓
8
问题
🎯
70%
及格要求
♾️
∞
时间
🔄
∞
尝试次数

📝 说明

  • 仔细阅读每个问题
  • 为每个问题选择最佳答案
  • 您可以随时重新参加测验
  • 您的进度将显示在顶部