JavaScript变量声明提升:深入理解执行顺序与结果

晚来风
晚来风
发布时间:2026-10-14 | 4人浏览过
原创

许多开发者在使用jQuery或第三方组件后,容易忽视JavaScript的原生基础。本文通过团队讨论中的典型题目,剖析变量声明提前的本质、赋值的延迟性以及全局与局部作用域的交互,揭示为什么看似相似的代码会输出不同结果。

核心结论:先答后析

先看最经典的代码案例及其直接结果:

  1. 问题一:typeof a 的结果为 number;typeof b 的结果为 undefined。
  2. 问题二:外部调用函数内部 typeof name 结果为 undefined,第二次为 string。
  3. 问题三:全局范围内 typeof name 均为 string。

准备条件与原理分析

JavaScript引擎在执行代码之前,会有一个特殊的“编译”或“预扫描”阶段。理解这个阶段的运作方式,是解决上述疑问的关键。

第1步:理解声明提升(Declaration Hoisting)

当JavaScript遇到使用 var 关键字定义的变量时,它会把这个变量的声明动作提升到当前作用域的顶部,但赋值动作依然停留在原处。

  • 对于函数级作用域(闭包内):即使变量是在函数中间甚至末尾才声明,引擎也会认为它在函数开头就被声明了,初始值为 undefined。
  • 对于全局作用域:同理,全局的 var 声明会被提升到顶层。但如果在全局未显式声明而直接赋值,则会创建一个全局属性(即隐式全局变量)。

第2步:区分全局与局部作用域

作用域的查找遵循“就近原则”,并沿着作用域链向上查找。如果在当前作用域找到了同名的声明(哪怕还没执行到赋值行),就不会去上一层寻找。

真实操作顺序与推导

我们将通过拆解实际的代码执行步骤,还原底层的逻辑。

第3步:分析第一组代码(含匿名函数)

观察以下代码的执行流程:

<script type="text/javascript">
    var a = 1;     // 语句1
    var a;         // 语句2
    alert(typeof a); // 语句3

    (function () {
        b = '-----'; // 语句4:注意这里没有var
        var b;       // 语句5
    })();
    alert(typeof b); // 语句6
</script>

推导过程:

  • 处理 var a: 引擎首先将所有的 var 声明提取到脚本顶部。此时变量 a 被声明但未初始化(值为 undefined)。
  • 执行语句1: 将值 1 赋给 a。
  • 执行语句2: var a 再次声明变量。由于引擎已经在此作用域声明过 a,所以这相当于一个重复声明。虽然声明动作发生了,但因为前面已经有了赋值,且本次没有新赋值,因此 a 的值保持不变,仍然是 1。
  • 处理语句3: 输出 typeof 1,即 number。
  • 处理闭包内的 b: 在匿名函数内部,引擎同样会先把 var b 提到函数顶部。
    - 此时函数内部的 b 是局部变量,初始为 undefined。
  • 执行语句4: b = '-----'。由于在当前函数作用域(局部)中已存在 b 的声明,此处的赋值是直接作用于局部变量的。这一步**并没有**创建全局变量 b。
  • 执行语句5: 局部变量声明完成(实际已被提升并跳过)。
  • 执行语句6: 跳出匿名函数,尝试访问外部的全局变量 b。由于全局从未被显式声明过 var b,且刚才赋值的是局部变量,所以全局 b 根本不存在,类型为 undefined。

第4步:分析第二组代码(带参数的同名变量)

这段代码最容易让初学者困惑:

<script type="text/javascript">
name = "aaa"; 
function test(){
    alert(typeof name);
    var name = "bbb";
    alert(typeof name);
}
test();
</script>

推导过程:

  • 外部环境: name = "aaa"。因为没有用 var,这在浏览器非严格模式下被视为创建了全局对象的属性。
  • 进入函数 test(): 函数执行前,JS引擎先扫描函数体内部的变量声明。
  • 声明提升: 发现 var name = "bbb",于是将整个声明部分 var name 提升到函数开头。此时函数内部有一个名为 name 的局部变量,值为 undefined。请注意,赋值 = "bbb" 仍然留在原来的位置。
  • 执行第一个 alert: 访问 name。因为当前函数作用域内已经声明了 name(尽管是局部的),引擎停止向上一级(全局)查找。返回的是函数内局部的 name 的当前状态,即 undefined。
  • 执行第二个 alert: 之前的赋值操作到达,name 被赋值为 "bbb",类型变为 string。

第5步:分析第三组代码(纯全局范围)

<script type="text/javascript">
alert(typeof name); // 输出 string
var name = "hello world";
alert(typeof name); // 输出 string
</script>

为何这次没有出现 undefined?

  • 提升规则: var name 被提升到整个 script 标签的最顶部。此时 name 初始为 undefined。
  • 关键点: 在全局作用域下,如果代码中存在多次对同一变量的“隐式”或“显式”关联,行为可能会受执行上下文环境影响。但在本例的标准推导中:
  • 实际上,按照标准 ECMAScript 定义,第一次 alert 理应输出 undefined。但如果出现 string 的情况,通常是因为该变量在更早的全局上下文中已经被定义过(或者代码片段在实际运行环境中不是独立隔离的)。
  • 更严谨的解释:如果在纯标准的现代浏览器控制台独立运行上述两行,第一次输出绝对是 undefined。原文中提到输出两次 string,可能是因为在前文已有 name="aaa" 的全局污染,导致在当前全局环境下,var name 的提升被旧的全局同名属性覆盖或未生效。这提醒我们,保持干净的命名空间至关重要。

简要总结

掌握变量声明提升的核心在于两点:一是记住只有 var 声明会被提及并置于作用域顶端,二是明确赋值动作永远留在原地。在处理涉及作用域切换(如从全局进入函数)的代码时,务必先检查函数体内是否存在重名变量。一旦存在同名声明,就会阻断对外部同名的引用,从而引发意料之外的 undefined 报错。

以上就是JavaScript变量声明提升的详细内容,更多关于JavaScript变量作用域的资料请关注本站其它相关文章!

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表