本文深入解析JavaScript中的声明提升机制,通过具体代码示例说明var变量声明与函数声明在编译阶段被提升至作用域顶部的行为。文章详细对比了函数声明与函数表达式的差异,探讨了同名声明的优先级规则,并解析了涉及闭包与遮蔽效应的复杂案例,帮助开发者避免常见的运行时错误。
JavaScript 声明提升机制详解:变量与函数声明的优先级
1 引言与基础概念
在深入探讨JavaScript的声明提升机制前,请观察以下示例:
console.log(a);
var a=2;
该代码的执行结果可能引发如下推测:
1. 抛出ReferenceError错误,提示a未定义;
2. 输出数值2;
3. 输出undefined。
实际运行结果为第三种情况,即输出undefined。
深入分析其原因:对于语句var a=2;,JavaScript引擎会将其解析为两个独立部分:
- 变量声明var a,会在代码编译阶段完成;
- 赋值操作a=2,则保留在原位,留待执行阶段处理。
所谓声明提升,是指JavaScript会将var变量声明及函数声明自动移至各自作用域的顶端,而赋值动作不受此影响。 据此,前述示例的实际处理流程如下:
var a;
console.log(a);
a=2;
需明确的是,不仅var声明会提升,函数声明同样具备提升特性。请参考以下示例:
foo();
function foo(){
console.log(a);
var a=2;
}
此例的执行结果为输出undefined。该代码在逻辑上等价于以下形式:
function foo(){
var a;
console.log(a);
a=2;
}
foo();
关于声明提升的常见问题
2 声明提升的常见疑问
2.1 函数表达式
首先考察一个涉及函数表达式的案例:
console.log(foo);
var foo=function(){}
上述代码执行后输出undefined。实质上,变量标识符foo的声明被提升了,但其赋值行为并未提升,可理解为如下结构:
var foo;
console.log(foo);
foo=function(){}
结论:函数声明会被提升,而函数表达式不会。
2.2 声明优先级
若在同一作用域内同时存在同名的函数声明与var变量声明,结果如何?请继续观察示例:
function a(){}
var a;
console.log(a);
var a;
function a(){}
console.log(a);
上述两种写法均输出a(){}。这表明,在同一作用域内若存在同名函数声明与var声明,函数声明的优先级更高。 另一种情形:若同一作用域内存在多个同名函数声明,后声明的将覆盖先前的声明。
练习题
3 练习题目
3.1 第一题
var getName = function() {
console.log(1);
}
function getName() {
console.log(2);
}
getName();
答案:输出1
解析:提升后的代码顺序如下
var getName;//与函数声明同名,故失效
function getName() {
console.log(2);
}
getName = function() {//赋值
console.log(1);
};
getName();
3.2 第二题
var a = 1;
function b(){
a = 10;
return;
function a(){
console.log(a);
}
}
b();
console.log(a);
答案:输出1
解析:关键在于理清声明提升的逻辑。 本题涉及几个核心要点:
函数b内部的a函数即便位于return语句之后,依然有效并发生声明提升,提升至b函数作用域的顶部。
部分读者(如我TAT)可能误判答案为10,认为最后调用b函数修改了全局变量a。实际上,因函数a发生声明提升,在函数b内部“遮蔽”了全局作用域中的变量a,故a=10;实为对函数a的重新赋值。为验证此点,可注释掉a函数,此时答案将变为10。
注:本题还涉及闭包知识,详细解析请参阅参考资料[2],讲解十分详尽。
var a;//全局变量a发生了声明提升
function b(){
function a(){//函数a发生声明提升
console.log(a);
}
a = 10;//因为函数a离它最近,因此赋值给函数a
return;
}
a = 1;//赋值给了全局变量a
b();
console.log(a);
4 参考资料
[1] 《你不知道的JavaScript》
[2] js中变量名与函数名重名的问题,Charles_Tian
[3] 函数声明与变量声明的提升机制优先级问题,一个菜鸟的奋斗史
以上为JavaScript声明提升的完整解析,更多相关内容敬请留意本站其他文章!