提升
先有鸡还是先有蛋
a = 2;
var a;
console.log(a); // 2
如上方代码案例一代码段所述
可以发现包括变量和函数在内的所有声明都会在任何代码执行前首先被处理。
var a;
a = 2;
console.log(a); // 2
案例一会被处理成如上方代码所示
第一部分是编译,第二部分是执行。
console.log(a); // undefined
var a = 2;
如左侧上方案例二代码段所示
var a = 2;
这个代码JavaScript实际会看成两个声明:var a;
和 a = 2;
。
var a;
console.log(a); // undefined
a = 2;
如案例二代码段所示
第一个定义声明是在进行的,第二个赋值声明会被留在原地等待。
foo();
function foo() {
console.log(a); // undefined
var a = 2;
}
// 提升后代码如下
function foo() {
var a;
console.log(a); // undefined
a = 2;
}
foo();
如案例三代码段所示
可以发现变量和函数声明在代码中出现的位置被“移动”到了最上面,这个过程就叫。即。只有声明本身会被提升,而赋值或其他运行逻辑会留在原地。如果提升改变了代码执行的顺序,会造成破坏。
foo(); // 不是ReferenceError,而是TypeError!
var foo = function bar() {
// ...
}
// 提升后代码如下
var foo
foo();
foo = function bar() {
// ...
}
如案例四代码段所示
可以看到变量标识符foo()
被提升并分配在全局作用域中,因此foo不会导致ReferenceError
。但是foo这个时候没有被赋值(如果它是一个函数声明而不是函数表达式,那就会赋值),foo()
由于对undefined
值进行调用而导致非法操作,所以抛出TypeError
异常。切记,在也在所在作用域中。
foo(); // TypeError;
bar(); // ReferenceError;
var foo = function bar() {
// ...
};
// 提升后如下所示
var foo;
foo(); // TypeError;
bar(); // ReferenceError;
foo = function () {
var bar = ...self...
// ...
}
函数优先
函数声明和变量声明都会被提升(重复声明的代码中,函数会首先被提升,然后才是变量)。
foo(); // 1
var foo;
function foo(){
console.log(1);
}
foo = function() {
console.log(2);
}
// 引擎会理解为如下形式
function foo() {
console.log(1);
}
foo(); //1
// var foo没有了是因为重复的声明,因此被忽略了,这是因为函数声明会被提升至普通变量之前
foo = function(){
console.log(2);
}
尽管重复的var声明会被忽略掉,但是出现在后面的函数声明可以覆盖前面的。
foo(); // 3
function foo() {
console.log(1);
}
var foo = function() {
console.log(2);
}
function foo() {
console.log(3)
}
// 引擎可以理解为如下
function foo() {
console.log(3)
}
foo();
foo = function() {
console.log(3)
}
var、let和const三者区别
- 变量提升
var
声明的变量存在变量提升,即变量可以在声明之前调用,值为undefined
。
let
和const
不存在变量提升,即它们所声明的变量一定要在声明执行后才能使用,否则报错。
// var
console.log(a) // undefined
var a = 10
// let
console.log(b) // Cannot access 'b' before initialization
let b = 10
// const
console.log(c) // Cannot access 'c' before initialization
const c = 10
- 块级作用域
var
不存在块级作用域,声明在全局作用域或函数作用域,let
和const
存在块级作用域。
// var
{
var a = 20
}
console.log(a) // 20
// let
{
let b = 20
}
console.log(b) // Uncaught ReferenceError: b is not defined
// const
{
const c = 20
}
console.log(c) // Uncaught ReferenceError: c is not defined
- 重复声明
var
变量可以在其范围内更新和重新声明,let
变量可以被更新但不能重新声明,const
变量既不能更新也不能重新声明。
/ var
var a = 10
var a = 20 // 20
// let
let b = 10
let b = 20 // Identifier 'b' has already been declared
// const
const c = 10
const c = 20 // Identifier 'c' has already been declared
- 修改声明的变量
var
和let
可以修改声明的变量,const
声明一个只读的常量。一旦声明,常量的值就不能改变。
// var
var a = 10
a = 20
console.log(a) // 20
//let
let b = 10
b = 20
console.log(b) // 20
// const
const c = 10
c = 20
console.log(c) // Uncaught TypeError: Assignment to constant variable
- 暂时性死区
var
不存在暂时性死区,let
和const
存在暂时性死区,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。
// var
console.log(a) // undefined
var a = 10
// let
console.log(b) // Cannot access 'b' before initialization
let b = 10
// const
console.log(c) // Cannot access 'c' before initialization
const c = 10
小结
以var a = 2;
为例子,无论作用域中声明出现在什么地方,都将在代码本身被执行前首先被处理,也就是提升,切记避免重复声明。能用const
的情况尽量使用const
,其他情况下大多数使用let
,所以避免使用var
额外补充
从代码的开始直到代码执行到声明变量的行之前,let
或const
声明的变量都处于“”(Temporal dead zone,)中。当变量处于暂时性死区之中时,其,尝试访问变量则ReferenceError
。当代码运行到声明变量所在行时,变量被为。如果声明中,则变量将被为undefined
,详见MDN或见ES6的let和const声明。