Javascript提高:变量提升(Hoisting)是 JavaScript 执行机制中最容易让新手困惑的核心概念-由Deepseek产生
核心本质:变量提升是 JS 引擎「编译阶段提前处理声明」的行为,目的是让代码执行时能访问到作用域内的声明,但不同声明方式表现不同;关键差异var提升后赋undefined、允许提前访问;let/const提升后有暂时性死区,提前访问报错;函数声明提升优先级最高,且整个函数体都提升;实践建议:日常开发用let/const替代var,避免提升导致的作用域问题;优先用const,需要改值时用let,彻底
·
变量提升(Hoisting)是 JavaScript 执行机制中最容易让新手困惑的核心概念之一,本质是 JS 引擎在代码正式执行前,会对变量和函数的声明进行“提前处理”的行为。下面我会从「是什么→为什么→不同声明方式的差异→实际坑点」逐步拆解,用通俗的语言和示例帮你彻底理解。
一、变量提升的本质:JS 执行的两个阶段
要理解变量提升,首先要知道 JS 代码执行分两步:
- 编译阶段(预解析):JS 引擎先扫描代码,把所有「变量声明」和「函数声明」提升到当前作用域的顶部(注意:只是声明提升,赋值不会提升)。
- 执行阶段:从上到下执行代码,给变量赋值、执行函数等。
可以把这个过程类比成:老师批改作业前,先把所有学生的名字(声明)登记在本子上(作用域顶部),批改时(执行阶段)再逐个核对答案(赋值)。
二、不同声明方式的变量提升差异(核心重点)
变量提升的表现因声明方式(var/let/const/函数声明)不同而完全不同,这也是新手最容易踩坑的地方。
1. var 的变量提升(最“直观”的提升)
- 规则:
var声明的变量,编译阶段会把「声明」提升到作用域顶部,赋值部分留在原地,提升后变量的默认值是undefined。 - 示例拆解:
原代码:
JS 引擎编译后的执行逻辑(伪代码):console.log(num); // undefined var num = 10; console.log(num); // 10var num; // 声明提升到顶部,默认值 undefined console.log(num); // 执行:输出 undefined num = 10; // 赋值留在原地执行 console.log(num); // 执行:输出 10 - 扩展:
var声明的变量,在全局作用域中会成为window对象的属性(浏览器环境):var a = 20; console.log(window.a); // 20(全局作用域的 var 变量挂载到 window)
2. let/const 的变量提升(“隐形”提升 + 暂时性死区)
ES6 引入的 let/const 并非“没有变量提升”,而是提升后处于「暂时性死区(TDZ)」,导致声明前无法访问。
- 规则:
- 编译阶段同样会提升
let/const的声明,但不会给变量赋默认值(区别于var的undefined); - 从作用域顶部到变量声明行之间的区域,称为「暂时性死区(TDZ)」,在此区域访问变量会直接报错,而非
undefined; - 只有执行到声明行后,变量才脱离 TDZ,可正常使用。
- 编译阶段同样会提升
- 示例拆解:
原代码(报错):
JS 引擎编译后的执行逻辑(伪代码):console.log(str); // 报错:Cannot access 'str' before initialization let str = "hello";// let str; // 声明提升,但无默认值,进入 TDZ console.log(str); // 执行:处于 TDZ,直接报错 str = "hello"; // 未执行到这一步就报错了 - const 额外注意:
const不仅提升后有 TDZ,还要求声明时必须赋值,否则编译阶段就报错:const num; // 报错:Missing initializer in const declaration(编译阶段就报错)
3. 函数声明的提升(比变量提升“更彻底”)
函数声明(function 函数名() {})的提升优先级高于变量提升,且整个函数体都会被提升(区别于变量只提升声明)。
- 示例1:函数声明提升
原代码:
编译后执行逻辑:fn(); // 输出 "函数执行了" function fn() { console.log("函数执行了"); }function fn() { // 整个函数提升到顶部 console.log("函数执行了"); } fn(); // 执行函数,输出内容 - 示例2:函数声明 vs var 变量提升(优先级)
原代码:
编译后执行逻辑:console.log(fn); // 输出函数体,而非 undefined var fn = 10; function fn() {}function fn() {} // 函数声明先提升 var fn; // var 声明提升(但同名变量声明会被忽略,因为函数已提升) console.log(fn); // 此时 fn 是函数,输出函数体 fn = 10; // 赋值留在原地(执行到这一步,fn 才变成 10) - 注意:函数表达式(
let fn = function() {})不具备“函数级提升”,只遵循变量提升规则:fn(); // 报错:fn is not a function(因为 let 提升后在 TDZ,且 fn 此时是变量而非函数) let fn = function() {};
三、变量提升的常见坑点(新手必避)
1. var 的块级作用域泄露
for (var i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i); // 输出 3、3、3(而非 0、1、2)
}, 100);
}
console.log(i); // 3(i 泄露到全局)
原因:var i 是函数/全局作用域,循环结束后 i=3,定时器执行时访问的是同一个全局 i。
解决:用 let 声明 i(块级作用域,每次循环创建新的 i)。
2. 暂时性死区的隐蔽场景
function test() {
console.log(a); // 报错(TDZ)
let a = 10;
}
test();
// 另一个隐蔽场景:typeof 不再安全
console.log(typeof b); // 报错(TDZ),而非 "undefined"
let b;
总结
- 核心本质:变量提升是 JS 引擎「编译阶段提前处理声明」的行为,目的是让代码执行时能访问到作用域内的声明,但不同声明方式表现不同;
- 关键差异:
var提升后赋undefined、允许提前访问;let/const提升后有暂时性死区,提前访问报错;函数声明提升优先级最高,且整个函数体都提升; - 实践建议:日常开发用
let/const替代var,避免提升导致的作用域问题;优先用const,需要改值时用let,彻底避开var的坑。
更多推荐



所有评论(0)