JavaScript 众多特性之一就是所谓的变量提升。
现在,如果你是 JavaScript 编码的新手,很可能还没有熟练地编写代码。因此, 对于变量提升可能也不熟悉。
变量提升到底是什么?
当 JavaScript 编译所有代码时,所有使用 var 的变量声明都被提升到它们的函数/局部作用域的顶部(如果在函数内部声明的话),或者提升到它们的全局作用域的顶部(如果在函数外部声明的话),而不管实际的声明是在哪里进行的。这就是我们所说的“提升”。请记住,这种“提升”实际上并不发生在你的代码中,而只是一种比喻,与JavaScript编译器如何读取你的代码有关。记住当我们想到“提升”的时候,我们可以想象任何被提升的东西都会被移动到顶部,但是实际上你的代码并不会被修改。
函数声明也会被提升,但是被提升到了最顶端,所以将位于所有变量声明之上。
在编译阶段变量和函数声明会被放入内存中,但是你在代码中编写它们的位置会保持不变。
说得够多了,给大家看一些代码的简单例子,来演示提升的影响。
第一个例子
如果我们在全局作用域编写以下代码:
console.log(myName);
var myName = 'rainyjune';
你认为输出结果是什么?
- Uncaught ReferenceError: myName is not defined
- rainyjune
- undefined
第三个才是正确答案。
正如我们前面提到的,当你的 JavaScript 代码在运行时,变量会移动到它们的作用域的顶部。然而,需要注意的一个关键点是,唯一移动到顶部的是变量声明,而不是赋值给变量的实际值。
我们以前面的代码为例,看看 JavaScript 编译器在运行时是如何执行代码的:
var myName;
console.log(myName);
myName = 'rainyjune';
这就是为什么 console.log 输出“undefined”,因为它检测到变量 myName 存在,但是 myName 直到第三行才被赋予一个值。
另一个例子
function hey() {
console.log('hey ' + myName);
};
hey();
var myName = 'rainyjune';
这里调用 hey() 函数仍然返回 undefined,因为实际上 JavaScript 编译器会在执行时这样执行:
function hey() {
console.log('hey ' + myName);
};
var myName;
hey();
myName = 'rainyjune';
所以当函数被调用时,它知道有一个变量叫做 myName,但是这个变量没有被赋予值。
函数声明的例子
catName("Chloe");
function catName(name) {
console.log("My cat's name is " + name);
}
/*
The result of the code above is: "My cat's name is Chloe"
*/
上述代码中,虽然我们在编写函数之前先调用了它,代码还是正确执行了。这是因为函数声明发生了提升。
只有声明被提升
JavaScript 只提升声明,而不提升初始化。如果一个变量在访问之后再声明和初始化,那么此刻它的值是 undefined。例如:
console.log(num); // 返回 undefined,因为只有声明被提升,此阶段还没有初始化
var num; // 声明
num = 6; // 初始化
下面是一个只初始化的例子。这样不会发生提升,所以访问变量会抛出 ReferenceError 异常。
console.log(num); // 抛出 ReferenceError 异常
num = 6; // 初始化
let 和 const 会发生提升吗?
在ECMAScript 2015中,let 和 const 声明的变量会发生提升,但在变量声明之前引用这个变量会导致一个 ReferenceError,因为从代码块开始到变量声明被处理期间,这个变量都处于一个“时间死区”。
console.log(x); // ReferenceError
let x = 3;