闭包和词法作用域
在 JavaScript 中,闭包(Closure) 是指一个函数能够访问并记住其词法作用域(Lexical Scope)中的变量,即使这个函数在其原始作用域之外被调用。
简单来说:
闭包 = 函数 + 它创建时所处的环境(外部作用域的变量)
一、直观例子
function outer() {
let count = 0; // 外部函数的局部变量
function inner() {
count++; // inner 访问了 outer 的变量 count
console.log(count);
}
return inner; // 返回内部函数
}
const fn = outer(); // outer 执行完毕,按理说 count 应该被销毁
fn(); // 输出 1
fn(); // 输出 2
- 虽然
outer()已经执行结束,但inner依然能“记住”并操作count。 - 这就是闭包:
inner捕获了outer的作用域。
二、闭包形成的关键条件
- 嵌套函数:内部函数定义在外部函数中;
- 内部函数引用了外部函数的变量;
- 外部函数返回了内部函数(或以某种方式将其暴露到外部)。
只要满足这三点,闭包就形成了。
三、闭包的本质:作用域链的保留
JavaScript 的函数在创建时,会绑定其所在的词法环境(Lexical Environment)。
即使外部函数执行完毕,只要内部函数还存在(未被垃圾回收),它所依赖的外部变量就不会被释放。
🧠 可以理解为:闭包让函数“随身携带”了一个小背包,里面装着它出生时周围的所有变量。
四、总结
| 特性 | 说明 |
|---|---|
| 是什么 | 函数 + 其创建时的词法环境 |
| 核心能力 | 让函数访问“已经退出”的作用域中的变量 |
| 优点 | 实现数据封装、状态保持、高阶函数等 |
| 风险 | 可能导致内存泄漏、意外的变量共享 |
💡 一句话理解闭包:
“函数记住了它出生的地方,并随时可以回去拿东西。”
闭包是 JavaScript 最强大也最易被误解的特性之一,掌握它,你就真正理解了 JS 的作用域和函数本质。
词法作用域
词法作用域(Lexical Scope) 是 JavaScript(以及其他许多编程语言)中变量查找规则的核心机制。它的核心思想是:
作用域在代码书写时(“词法阶段”)就已经确定了,与运行时的调用位置无关。
一、“词法”是什么意思?
- “词法”(Lexical)指的是源代码的书写结构。
- 引擎在解析代码时(还没执行),就根据代码的嵌套结构静态地确定了每个函数的作用域链。
✅ 所以词法作用域也叫 静态作用域(Static Scope)。
二、对比:动态作用域(Dynamic Scope)
为了更清楚,我们对比一下动态作用域(JavaScript 不使用这种):
// 假设 JS 是动态作用域(实际不是!)
let x = 1;
function foo() {
console.log(x); // 动态作用域下,看谁调用了我
}
function bar() {
let x = 2;
foo(); // 如果是动态作用域,这里会输出 2
}
bar(); // 实际 JS 输出 1(因为是词法作用域)
- 动态作用域:函数执行时,沿着调用栈向上找变量。
- 词法作用域:函数执行时,沿着定义时的嵌套结构(作用域链)找变量。
🌟 JavaScript 是词法作用域,不是动态作用域。
三、常见误区澄清
❌ 误区:“this 的值由词法作用域决定”
- 错误!
this是动态绑定的,由调用方式决定(如obj.method()中this === obj)。 - 词法作用域只管变量查找(如
x,y),不管this。
✅ 正确:箭头函数的 this 是词法的
- 箭头函数没有自己的 this,它会继承外层作用域的 this —— 这里的“外层作用域”是词法上的。
- 所以箭头函数的
this表现得像“词法变量”。