闭包
你已经懂了作用域:从这里能看见哪些名字。再进一步:外层调用结束后,内层函数仍可继续使用外层名字。这种持久联系就是闭包(closure)。
闭包是什么意思
当函数在另一个函数内部被创建,并且之后仍能访问外层的绑定,就形成了闭包。外层调用可能已经结束;内层函数仍然「记得」那些名字。你不会在代码里写 closure 这个词——你写嵌套函数,JavaScript 会把需要的外层绑定保持存活。
盯住三块
📊 外层函数、绑定与内层函数
| 部分 | 作用 | 初学者提示 |
|---|---|---|
| 外层函数 | 运行一次(或多次),常常返回内层函数 | 「准备」那一次调用 |
| 外层绑定 | 外层里的参数或 let / const |
内层仍然需要的值 |
| 内层函数 | 之后调用时使用该绑定 | 你要保留的那个函数 |
能指出这三块,就能解释几乎所有初学者闭包例子。惊喜不在嵌套本身——而在外层 return 之后,外层绑定仍然有用。
function makeLine(title) {
function show() {
console.log(title);
}
show();
}
makeLine("Shop");
调用进行时,内层朝外查找
show 没有本地的 title。查找向外走到 makeLine,找到参数。这是普通的嵌套作用域。闭包再进一步:在 makeLine 返回之后,把 show(或它的孪生)留到以后用。
function makeLine(title) {
return function show() {
console.log(title);
};
}
let printShop = makeLine("Shop");
printShop();
外层调用结束——记忆仍在
makeLine("Shop") 返回一个函数。之后调用 printShop,它仍打印 Shop。那次已结束调用里的参数 title 仍然可达。这种持久捕获就是闭包。
function makeGreeter(name) {
return function () {
return "Hello, " + name;
};
}
let hiSara = makeGreeter("Sara");
console.log(hiSara());
工厂返回现成函数
makeGreeter 是个小工厂:一次外层调用,返回一个定制函数。hiSara 总是问候 Sara,因为它闭合了那个 name。换掉传给 makeGreeter 的参数,就得到另一个问候者——同一模式,新的记忆。
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。没有全局变量,计数器也能「记住」。
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 从中间开始。除非你故意共享一个外层绑定,闭包不会自动共用一袋状态。
把闭合的值显示在页面上
用工厂做一个问候者,调用一次,把返回的字符串放进段落。
<!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)——把相关值收在一个名字下。
练习步骤
- 写一个返回内层函数的函数,内层日志记录外层参数。
- 把返回的函数存进变量,等外层调用结束后再调用它。
- 构建
makeGreeter("Lina")并日志返回的字符串。 - 创建一个计数器;调用两次,确认先是
1再是2。 - 创建两个计数器;只给第一个加两下,确认第二个仍从
1开始。 - 在 HTML 演示里只改名字参数,刷新,读段落。
常见踩坑
- 以为外层局部变量在
return瞬间消失——内层仍能看见 - 以为每次
makeCounter()都共享同一个n——通常每次调用各有记忆 - 以为「闭包」只等于关键字
return——return可以交回内层,记忆是被闭合的绑定 - 为了「简单」把每个要记住的值都放到全局对象上
- 在这里期待完整的对象课——把属性收在一个值下是下一页
小结
- 闭包让内层函数在外层调用结束后仍能使用外层名字
- 你写嵌套函数;JavaScript 保持所需绑定存活
- 工厂返回记住准备值的定制函数
- 计数器把私有
n藏在返回函数后面 - 分开的外层调用通常意味着分开的记忆
- 页面更新可以显示由闭合名字产生的字符串
- 下一课:对象——相关值收在一个名字下
🧠 测试您的知识
测试您的知识
通过这个互动测验挑战自己,看看你对这个主题的理解程度如何
📝 说明
- 仔细阅读每个问题
- 为每个问题选择最佳答案
- 您可以随时重新参加测验
- 您的进度将显示在顶部