本文深入解析 JavaScript 中的声明提升(Hoisting)机制。详细对比了 function、var、let、const 以及 class 在提升行为上的差异,重点阐述了 var 的赋值不提升特性以及 let/const 形成的暂时性死区(TDZ),帮助开发者避免常见的变量访问错误。
函数的提升
声明提升(Hoisting)是 JavaScript 引擎解析代码时的一项核心机制,它会将函数和变量的声明语句自动提取到其所在作用域的顶部。
这意味着在 JavaScript 中,我们可以在函数正式声明之前调用该函数。
say();
function say() {
console.log("Hello");
}
解析器在扫描作用域代码时,会优先处理函数声明,将其移至代码执行部分之前。因此,解析器眼中的代码逻辑如下所示:
function say() {
console.log("Hello");
}
say();
除了常规函数外,async function、function * 以及 async function * 同样具备这种提升效果。
var 变量声明提升
使用 var 关键字声明的变量会发生提升,但变量的赋值操作不会被提升。
console.log(foo); // undefined
var foo = "bar";
console.log(foo); // 'bar'
上述代码经过解析后的实际执行顺序如下:
var foo;
console.log(foo);
foo = "bar";
console.log(foo);
这种机制可能会引发一些令人困惑的问题:
var x = "x in global";
(function () {
// 这里期望读取全局变量
console.log(x); // 结果为undefined. 🙀
/* ... */
// 在函数内某处
var x = "x in function";
})();
在早期开发实践中,为了规避此类提升带来的隐患,开发者通常习惯将 var 声明统一放置在作用域的起始位置。
var x='x';
var y='y';
function (){
var x;
var foo;
// ...
}
如今,我们更倾向于不使用 var,转而采用更规范的 let 和 const 来声明变量。
let 和 const 变量声明与暂时性死区
那么,let 和 const 是否完全不存在变量提升呢?—— 答案并非如此。
请参考以下示例:
const x = "x in global";
(function () {
// 这里期望读取全局变量
console.log(x); // ReferenceError: Cannot access 'x' before initialization
/* ... */
// 在函数内某处
const x = "x in function";
})();
执行时会报错,这表明 const x = "x in function"; 这一行代码影响了作用域内上方区域的代码执行。
解析器会扫描当前作用域下的 const 和 let 声明,如果在声明语句之前尝试访问变量名,将会触发 ReferenceError。这种机制有效避免了前述 var 提升带来的问题,并消除了代码语义的模糊性。
class 关键字也具备类似效果,对 new 一个未声明的类进行实例化时,同样会引发 ReferenceError。
new MyClass(); // ReferenceError: Cannot access 'MyClass' before initialization
class MyClass {}
关于这种情况是否属于提升存在争议:有人认为声明和赋值均未提前,故不属于提升;也有人认为这些语句在执行前已产生影响,属于标识符的提升。个人倾向于后一种观点,即这是针对(变量和类名)标识符的提升行为。
总结
部分 JavaScript 声明语句的效果会覆盖其所在整个作用域,这种现象被称为提升。
主要包含以下 3 种类型的提升行为:
- 使用
function关键字声明时,声明和赋值均会被提升。 - 使用
var关键字声明时,仅声明提升,赋值不提升。 - 使用
let、const以及class时,标识符会被提升并形成暂时性死区,此时声明和赋值均不会提前生效。
以上就是 JavaScript 中声明提升机制的详细内容,更多关于 JavaScript 声明提升的资料请关注本站其它相关文章!