JavaScript变量声明提升:深入理解执行顺序与结果
许多开发者在使用jQuery或第三方组件后,容易忽视JavaScript的原生基础。本文通过团队讨论中的典型题目,剖析变量声明提前的本质、赋值的延迟性以及全局与局部作用域的交互,揭示为什么看似相似的代码会输出不同结果。
核心结论:先答后析
先看最经典的代码案例及其直接结果:
- 问题一:
typeof a的结果为 number;typeof b的结果为 undefined。 - 问题二:外部调用函数内部
typeof name结果为 undefined,第二次为 string。 - 问题三:全局范围内
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变量作用域的资料请关注本站其它相关文章!
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









