JavaScript 中的变量提升
2025年7月16日大约 3 分钟
JavaScript 中的变量提升
什么是变量提升
变量提升是指:变量声明会被提升到当前作用域的顶部(函数或全局),但初始化不会被提升。
在 js 中使用通常有两种行为会发生变量提升
- 使用 Var 定义的变量
console.log(a) //undefined (访问到变量但是未赋值状态)
var a = 10
/**
* 等价于 以下情况
*/
var a
console.log(a)
a = 10
- 函数声明
fn() // 不会报错,输出Hi,fn被整体提升到顶部
function fn() {
console.log('Hi')
}
为什么会发生变量提升
变量提升的存在主要是历史遗留问题,早期的js在设计上强调的是宽松,容错性强,而不是类型安全。但是随着js的发展逐步规范变量作用域。
在 ES6中新增了let/const,class(let的语法糖),引入了暂时性死区的概念,用来平衡类型安全的的严格性,以及变量提升所存在的问题。
/**
* 📌 JavaScript 变量提升经典问题示例
*
* 使用 `var` 声明变量,由于其函数作用域特性,
* 在异步任务执行时,变量值已被修改,从而引发意料之外的结果。
*/
// 示例 1:使用 var(输出 3 3 3)
for (var x = 0; x < 3; x++) {
setTimeout(() => console.log(x), 0)
}
// 原因:
// - `var` 是函数作用域,整个循环中只有一个 `x`。
// - `setTimeout` 是异步的,执行回调时,x 已经变成了 3。
// - 所以每次打印的都是最终的 x 值:3。
/**
* ✅ 改进方式:使用 let(输出 0 1 2)
*
* 使用 `let` 声明的变量具有块级作用域,每次循环都会创建一个新的作用域,
* 并绑定当前循环的值,因此异步回调中能访问到各自独立的变量值。
*/
// 示例 2:使用 let(输出 0 1 2)
for (let x = 0; x < 3; x++) {
setTimeout(() => console.log(x), 0)
}
// 原因:
// - `let` 是块级作用域,每次迭代都会创建一个新的 x。
// - 每个 setTimeout 回调都“闭包”了当时的 x 值。
/**
* ✅ 另一种解决方式:使用 IIFE(输出 0 1 2)
*
* 在 ES6 出现之前,我们常用 IIFE(立即执行函数表达式)来手动捕获变量快照。
*/
// 示例 3:使用 IIFE 和 var(输出 0 1 2)
for (var x = 0; x < 3; x++) {
(function(xCopy) {
setTimeout(() => console.log(xCopy), 0)
})(x)
}
// 原因:
// - 每次循环时,立即调用函数并传入当前 x 值。
// - 闭包捕获的是 `xCopy`,是独立的副本。
/**
* ✅ 现代方式:使用 setTimeout 第三个参数(输出 0 1 2)
*
* setTimeout 可接受额外参数,这些参数会作为回调的参数传入。
*/
// 示例 4:传参方式(输出 0 1 2)
for (var x = 0; x < 3; x++) {
setTimeout((val) => console.log(val), 0, x)
}
// 原因:
// - 虽然 `x` 是 var,但每次循环将当前值作为参数传给回调,避免闭包共享。