← 返回首页
变量提升、作用域、闭包原理与场景
这三个概念是 JavaScript 核心机制的基石,它们共同构成了 JS 的执行上下文模型。理解它们不仅能帮你应对面试,更能让你写出没有 Bug 的优雅代码。
一、 作用域(Scope):变量的“活动范围”
作用域决定了代码中变量和其他资源的可见性和生命周期。
- 全局作用域:在任何函数或代码块外部声明的变量,整个程序都能访问。
- 函数作用域:在函数内部声明的变量,仅在函数内部有效。
- 块级作用域(ES6 新增):使用
let和const声明的变量,仅在{}代码块内部有效,解决了var带来的变量泄露问题。
二、 变量提升(Hoisting):代码的“预编译阶段”
JavaScript 引擎在执行代码前,会先进行解析。在这个阶段,它会将变量的声明和函数的声明“提升”到当前作用域的顶部。
var的提升:声明会被提升,但赋值不会。console.log(a); // undefined (不会报错,因为声明被提升了) var a = 10; // 相当于 var a; 被提升到了这里let/const的提升:它们同样会被提升,但在声明语句执行之前,它们处于**“暂时性死区”(TDZ)**,此时访问会直接报错。console.log(b); // ReferenceError: Cannot access 'b' before initialization let b = 20;- 函数声明的提升:整个函数体都会被提升,可以在声明前调用。但函数表达式(如
var fn = function() {})只会提升变量名,不会提升函数体。
三、 闭包(Closure):函数与它诞生时的“词法环境”
闭包原理:当一个内部函数被返回到外部并被调用时,它不仅包含函数本身的代码,还“记住”了它诞生时的外部变量环境。即使外部函数已经执行完毕,其作用域中的变量依然存活在内存中,不会被垃圾回收机制(GC)销毁。
闭包场景:
- 数据私有化(封装):模拟面向对象中的私有变量,防止全局变量污染。
function createCounter() { let count = 0; // 外部无法直接修改 return { increment: () => ++count, getCount: () => count }; } - 函数柯里化与偏函数:固定部分参数,返回一个接收剩余参数的新函数。
- 防抖(Debounce)与节流(Throttle):利用闭包保存定时器 ID,确保多次触发时只执行最后一次或按固定频率执行。
经典高频面试题:循环中的闭包陷阱
// 使用 var:输出 5 个 5
for (var i = 0; i < 5; i++) {
setTimeout(() => console.log(i), 1000);
}
// 使用 let:输出 0, 1, 2, 3, 4
for (let i = 0; i < 5; i++) {
setTimeout(() => console.log(i), 1000);
}
原理解析:var 没有块级作用域,循环结束后全局只有一个 i=5,5个定时器回调引用的都是同一个 i。而 let 具有块级作用域,JS 引擎在底层会为每一次循环迭代创建一个独立的词法环境(相当于隐藏了一个闭包),因此每个定时器回调都能捕获到属于自己的 i 值。