作用域是变量定义的可访问范围。
类型 | 说明 |
---|---|
全局作用域 | 在任何地方都能访问的变量(如浏览器中 window ) |
函数作用域 | 函数内部定义的变量只能在函数内部访问 |
块级作用域(ES6) | 使用 let 、const 创建的变量,限制在 {} 中 |
var a = 1; // 全局作用域
function foo() {
var b = 2; // 函数作用域
if (true) {
let c = 3; // 块级作用域
}
console.log(c); // ❌ ReferenceError
}
变量和函数声明会“提升”到当前作用域顶部,但只有 var
和 function
会提升,let
/const
不会。
console.log(a); // undefined(提升了但未赋值)
var a = 10;
console.log(b); // ❌ ReferenceError
let b = 20;
闭包是函数与其词法作用域的组合。
当函数在其定义的作用域外被调用,仍然可以访问其原始作用域中的变量,这就是闭包。
function makeCounter() {
let count = 0;
return function() {
count++;
return count;
};
}
const counter = makeCounter();
console.log(counter()); // 1
console.log(counter()); // 2
此处 count
是在外部函数中定义的变量,返回的匿名函数在执行时依旧能访问它。
应用场景 | 示例或说明 |
---|---|
数据缓存 | 利用闭包保存函数状态(如计数器) |
模块化开发 | 封装变量,避免全局污染 |
函数柯里化 | 分步传参 |
防抖/节流函数 | 闭包保存计时器 ID |
创建私有变量 | JS 没有真正的私有变量,闭包是替代方案 |
答:闭包是函数可以“记住”并访问其定义时的词法作用域,即使这个函数在当前词法作用域之外被调用。
答:闭包可能会导致不必要的内存占用,因为其引用的外部变量不会被 GC 回收。只要不手动清理引用或及时释放,确实可能造成内存泄漏。
function once(fn) {
let called = false;
return function(...args) {
if (!called) {
called = true;
return fn.apply(this, args);
}
};
}
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 1000);
}
答:输出 3、3、3
因为 var
没有块级作用域,setTimeout
延迟执行时,i
已变为 3。
✅ 解决方法:使用闭包或 let:
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 1000);
}
let
, const
)更容易控制作用域;掌握作用域与闭包不仅是写出高质量 JavaScript 的基础,也是面试中区分中高级开发者的关键技能。理解作用域链、变量提升、闭包生命周期,能帮助你更清晰地分析代码执行过程。