news 2026/4/17 7:23:21

JavaScript 声明提升

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
JavaScript 声明提升

JavaScript 声明提升 (Hoisting) 学习笔记

声明提升是 JavaScript 中一个非常独特且容易让人困惑的机制。它指的是:在代码执行之前,JavaScript 引擎会将变量和函数的声明部分“提升”到当前作用域的顶部,但赋值部分不会提升。

这意味着,无论你在代码的哪个位置声明变量或函数,它们都像是在作用域顶部被声明了一样。


一、核心机制:提升什么?不提升什么?

理解提升的关键在于区分声明 (Declaration)赋值 (Initialization/Assignment)

声明方式声明提升?赋值提升?提升后的行为
var✅ 是❌ 否初始化为undefined
let/const✅ 是 (语法层面)❌ 否进入暂时性死区 (TDZ),访问报错
function(声明)✅ 是✅ 是 (整个函数体)函数完全可用
function(表达式)✅ 是 (变量部分)❌ 否变量为undefined,调用报错

二、var的声明提升

var是最典型的提升案例。代码执行前,var声明会被提升到作用域顶部,并初始化为undefined

1. 基本示例

console.log(age);// 输出:undefined (不报错)varage=25;console.log(age);// 输出:25

引擎实际执行顺序(逻辑上):

varage;// 声明提升,初始化为 undefinedconsole.log(age);// undefinedage=25;// 赋值console.log(age);// 25

2. 函数内的提升

functiontest(){console.log(name);// undefinedvarname="Alice";console.log(name);// "Alice"}test();

等价于:

functiontest(){varname;// 提升console.log(name);// undefinedname="Alice";console.log(name);// "Alice"}

3. 重复声明

var允许在同一作用域内重复声明同一个变量,不会报错,后面的声明会被忽略。

vara=1;vara=2;console.log(a);// 2

三、letconst的声明提升与 TDZ

letconst也会被提升,但它们的行为与var截然不同。

1. 暂时性死区 (Temporal Dead Zone, TDZ)

在代码执行流到达声明语句之前,变量处于“未初始化”状态。此时访问该变量会抛出ReferenceError。这个区域称为TDZ

console.log(age);// ❌ ReferenceError: Cannot access 'age' before initializationletage=25;

为什么?
虽然声明被提升了,但初始化(赋值)没有。在let age = 25;这一行执行之前,age存在于作用域中,但不可访问。

2.const必须初始化

const声明必须在声明时立即赋值,否则报错。

constPI;// ❌ SyntaxError: Missing initializer in const declarationconstPI=3.14;// ✅

3. 块级作用域的影响

letconst是块级作用域(Block Scope),提升仅限于当前块{}内。

if(true){console.log(x);// ❌ ReferenceErrorletx=10;}

四、函数声明 vs 函数表达式

这是提升中最容易混淆的地方。

1. 函数声明 (Function Declaration)

完全提升。函数名和函数体都会被提升到作用域顶部。可以在声明之前调用。

sayHello();// ✅ 输出:Hello!functionsayHello(){console.log("Hello!");}

等价于:

functionsayHello(){console.log("Hello!");}sayHello();

2. 函数表达式 (Function Expression)

只提升变量声明,不提升赋值。变量初始化为undefined。如果在赋值前调用,会报错TypeError

sayHi();// ❌ TypeError: sayHi is not a functionvarsayHi=function(){console.log("Hi!");};

等价于:

varsayHi;// 声明提升,值为 undefinedsayHi();// ❌ TypeError: sayHi is not a functionsayHi=function(){...};// 赋值

3. 箭头函数

箭头函数本质上是函数表达式,行为与var+ 函数表达式一致。

constadd=(a,b)=>a+b;// 如果用 varvaradd=(a,b)=>a+b;// 在赋值前调用 add 会报错

五、混合提升场景

varletconst、函数声明混用时,提升优先级和规则如下:

  1. 函数声明优先级最高(完全提升)。
  2. var声明次之(提升并初始化为undefined)。
  3. let/const最后(提升但进入 TDZ)。

示例 1:同名变量冲突

console.log(a);// undefined (var 提升)vara=1;leta=2;// ❌ SyntaxError: Identifier 'a' has already been declared

注意:在同一作用域内,不能同时用varlet/const声明同名变量。

示例 2:函数与变量同名

console.log(test);// 输出函数定义 (函数声明优先)vartest=100;console.log(test);// 输出 100 (var 赋值覆盖了函数)functiontest(){return"function";}

执行流程:

  1. 函数test被完全提升。
  2. var test声明被提升(但被函数声明覆盖,不重复声明)。
  3. 执行console.log(test)-> 输出函数。
  4. 执行var test = 100-> 变量test被赋值为 100。
  5. 执行console.log(test)-> 输出 100。

六、常见陷阱与最佳实践

1. 陷阱:在声明前使用var

// 容易写出难以调试的 bugif(condition){vardata=fetchData();}console.log(data);// 即使 condition 为 false,data 也是 undefined,而不是报错

解决:使用letconst,利用 TDZ 在错误使用时立即报错。

2. 陷阱:循环中的var

varfuncs=[];for(vari=0;i<3;i++){funcs.push(function(){console.log(i);});}funcs[0]();// 输出 3 (所有函数共享同一个 i)funcs[1]();// 输出 3funcs[2]();// 输出 3

解决:使用letlet是块级作用域,每次循环都会创建新的i

letfuncs=[];for(leti=0;i<3;i++){funcs.push(function(){console.log(i);});}funcs[0]();// 输出 0funcs[1]();// 输出 1funcs[2]();// 输出 2

3. 最佳实践

  1. 始终使用letconst:避免var带来的提升陷阱和变量污染。
  2. 声明在前,使用在后:养成在作用域顶部声明变量的习惯(虽然现代 JS 不强制,但可读性好)。
  3. 不要依赖提升:即使函数声明可以提前调用,也建议将函数定义放在调用之前,提高代码可读性。
  4. 利用 TDZ:如果代码在声明前访问了变量,let/const会立即报错,帮助你快速发现逻辑错误。

七、总结

特性varlet/const函数声明
声明提升✅ 是✅ 是 (语法上)✅ 是
初始化提升✅ 是 (undefined)❌ 否 (TDZ)✅ 是 (整个函数)
作用域函数作用域块级作用域函数作用域
重复声明✅ 允许❌ 不允许❌ 不允许
访问未初始化变量返回undefined抛出ReferenceErrorN/A

一句话总结

JavaScript 引擎在执行代码前会先扫描并提升声明。var提升并初始化为undefinedlet/const提升但进入“暂时性死区”,函数声明则完全提升。为了代码的健壮性和可读性,请优先使用letconst,并遵循“先声明后使用”的原则

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/17 7:20:18

在旧货市场买东西需要避哪些坑?

想省钱买二手家具家电&#xff0c;去旧货市场淘货是不少人的选择。但旧货市场水不浅&#xff0c;一不小心就踩坑。今天整理了几个最常见的坑&#xff0c;还有大家问得最多的问题&#xff0c;一次性说清楚。避坑第一点&#xff1a;警惕成色隐瞒&#xff0c;别把残次品当好货收不…

作者头像 李华
网站建设 2026/4/17 7:15:19

从‘原理公式’到‘跑通代码’:MATLAB功率谱估计保姆级避坑指南(附BT/Welch法完整脚本)

从数学公式到可执行代码&#xff1a;MATLAB功率谱估计实战避坑手册 当你第一次在《数字信号处理》课本上看到维纳-辛钦定理时&#xff0c;可能觉得它优雅而完美——直到你尝试用MATLAB实现它。那些在纸上推导时清晰明了的公式&#xff0c;一旦转化为代码就会冒出各种意想不到的…

作者头像 李华
网站建设 2026/4/17 7:13:32

小白也能懂:一键部署Fish-Speech-1.5,让AI开口说13国语言

小白也能懂&#xff1a;一键部署Fish-Speech-1.5&#xff0c;让AI开口说13国语言 1. 认识Fish-Speech-1.5语音合成模型 1.1 什么是Fish-Speech-1.5 Fish-Speech-1.5是目前最先进的开源文本转语音(TTS)模型之一&#xff0c;它基于超过100万小时的多种语言音频数据训练而成。简…

作者头像 李华
网站建设 2026/4/17 7:13:31

大模型中的Function_call与Agent:从功能调用到智能决策的演进之路

1. 从工具到管家&#xff1a;Function_call与Agent的本质差异 第一次接触大模型开发时&#xff0c;我花了整整两周才搞明白Function_call和Agent的区别。这就像分清楚"螺丝刀"和"修车师傅"的关系——前者是完成特定任务的工具&#xff0c;后者是能自主决策…

作者头像 李华