在JavaScript 中,提升(hoisting) 是一个有趣的现象,它指的是变量或函数声明会被移动到它们所在作用域的顶部,在执行前就生效了。这意味着可以在声明之前就使用一个变量或函数。
用 var
提升变量
通过一个例子我们可以观察到提升行为是如何运作的,比如用 var
声明的变量。看看下面这段代码:
console.log(myVar); // 输出: undefined
var myVar = 10;
console.log(myVar); // 输出: 10
初看之下,你可能会觉得 console.log(myVar)
在声明之前调用会抛出错误,因为此时 myVar
还没有被声明。但是由于提升的存在,myVar
的声明被移到了作用域的顶部,使得这段代码能顺利运行,不会报错。实际上,就像这样写了一样:
var myVar;
console.log (myVar); // 输出: undefined
myVar = 10;
console.log (myVar); // 输出: 10
只有 var myVar
的声明部分被提升了,而赋值语句 myVar = 10
并没有。所以当第一次打印变量时,它的值为 undefined
,因为虽然变量名已经被声明了,但它还没有被赋予一个值。
var
的问题
var
存在一个问题是,它的作用域并不是局限于它被声明的那个块内。这可能会导致在循环或者像 if
语句这样的控制结构中出现一些非预期的行为。
if (true) {
var testVar = "我在块内部";
}
console.log (testVar); // 输出: "我在块内部"
即使 testVar
是在“if 块”内声明的,它也能在外部被使用。正如前面提到的,var
缺乏块级作用域,而是具有函数级或全局的作用域。
使用 let
和 const
提升
为了克服 var
带来的问题,JavaScript 在 ES6 中引入了 let
和 const
。这些关键字同样会被提升,但有一个显著的区别:它们的实际化是在程序需要变量的时候,也就是当它们的声明语句被执行时。在此之前,这些变量处于暂时性死区(TDZ),如果尝试访问它们会导致 Reference Error
。
让我们来看个例子:
console.log (myLet); // 参考错误: 在初始化前无法访问 'myLet'
let myLet = 5;
这里,let
被移动到了块的顶部;然而,直到执行到 let myLet = 5
时才对它进行赋值。这意味着尽管变量已经在作用域内——也就是说已经被声明了——但在给它赋值之前不能使用它。
const
也有同样的行为:
console.log (myConst); // 参考错误: 在初始化前无法访问 'myConst'
const myConst = 10;
在这个例子中,const
变量被声明了但没有定义,因此如果脚本试图在定义前使用它,就会抛出 Reference Error
。
用 let
和 const
实现块级作用域
如果你希望一个变量的作用域仅限于某个块内,那么推荐使用 let
和 const
。
if (true) {
let blockVar = "我在块内部";
}
console.log (blockVar); // 参考错误: blockVar 没有被定义
在这个情况下,你只能在它最初声明的那个块内引用 blockVar
。一旦试图在 if 块之外访问它,就会导致 Reference Error
。
函数的提升
JavaScript 中的 函数 也会被提升,但是在它们如何被提升方面,函数声明和函数表达式之间存在差异。
函数声明
对于函数声明来说,函数的名字以及函数体都会被提升到作用域的顶层。因此,即使在声明之前调用函数也是可以的:
Greet(); // 输出: "Hello, world!"
function Greet () {
console.log ("Hello, world!") ;
}
在这个例子中,Greet
函数的标识符被提升到了最顶部,允许在运行时访问其定义。值得注意的是,函数调用出现在代码中的实际定义之前,但由于提升,这依然可以工作。
函数表达式
函数表达式则不会完全被提升,这意味着如果你试图在一个函数表达式在作用域内定义之前调用它,可能会遇到错误。具体来说,虽然分配函数的变量被提升了,但函数本身并没有。
sayHello (); // 类型错误: say Hello 不是一个函数
var sayHello = function () {
console.log("Hello!");
};
这段代码表现得好像它是这样写的:
var sayHello;
sayHello (); // 类型错误: say Hello 不是一个函数
sayHello = function () {
console.log("Hello!");
};
在上面的例子中,var sayHello
的声明被提升到了作用域的顶部。但是当我们尝试执行分配给 sayHello
的函数时,由于此时还没有赋值,就会导致 类型错误
。因此,你不能在定义之前调用一个函数表达式。
总结
提升是 JavaScript 中一个有趣的概念,它既可以让你作为 JS 开发者的生活变得更简单,也可能更复杂。理解提升是如何在 var
,let
,const
和函数声明之间工作的可以帮助你避免 JavaScript 代码中的棘手 bug。