Skip to content

提升

先有鸡还是先有蛋

javascript
a = 2
var a;
console.log(a); // 2

如上方代码案例一代码段所述

可以发现包括变量和函数在内所有声明都会在任何代码执行前首先被处理

javascript
var a;
a = 2;
console.log(a); // 2

案例一会被处理成如上方代码所示

第一部分编译第二部分执行

javascript
console.log(a); // undefined
var a = 2;

如左侧上方案例二代码段所示

var a = 2; 这个代码JavaScript实际会看成两个声明:var a;a = 2;

javascript
var a;
console.log(a); // undefined
a = 2;

如案例二代码段所示

第一个定义声明是在进行的,第二个赋值声明会被留在原地等待

javascript
foo();

function foo() {
  console.log(a); // undefined
  var a = 2;
}
// 提升后代码如下

function foo() {
  var a;
  console.log(a); // undefined
  a = 2;
}

foo();

如案例三代码段所示

可以发现变量函数声明在代码中出现的位置被“移动”到了最上面,这个过程就叫。即。只有声明本身会被提升,而赋值或其他运行逻辑留在原地。如果提升改变了代码执行的顺序,会造成破坏。

javascript
foo(); // 不是ReferenceError,而是TypeError!
var foo = function bar() {
  // ...
}
// 提升后代码如下

var foo
foo();
foo = function bar() {
  // ...
}

如案例四代码段所示

可以看到变量标识符foo()提升分配在全局作用域中,因此foo不会导致ReferenceError。但是foo这个时候没有被赋值(如果它是一个函数声明而不是函数表达式,那就会赋值),foo()由于对undefined值进行调用而导致非法操作,所以抛出TypeError异常。切记在所在作用域中

javascript
foo(); // TypeError;
bar(); // ReferenceError;
var foo = function bar() {
  // ...
};
// 提升后如下所示
var foo;
foo(); // TypeError;
bar(); // ReferenceError;
foo = function () {
  var bar = ...self...
  // ...
}

函数优先

函数声明变量声明都会被提升(重复声明的代码中,函数首先被提升,然后才是变量)。

javascript
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声明会被忽略掉,但是出现在后面的函数声明可以覆盖前面的。

javascript
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

letconst不存在变量提升,即它们所声明的变量一定要在声明执行后才能使用,否则报错。

javascript
// 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不存在块级作用域,声明在全局作用域或函数作用域,letconst存在块级作用域。

javascript
// 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变量既不能更新也不能重新声明。

javascript
/ 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
  • 修改声明的变量

varlet可以修改声明的变量,const声明一个只读的常量。一旦声明,常量的值就不能改变。

javascript
// 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不存在暂时性死区,letconst存在暂时性死区,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。

javascript
// 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

额外补充

代码的开始直到代码执行到声明变量的行之前,letconst声明的变量都处于“”(Temporal dead zone,)中。当变量处于暂时性死区之中时,其,尝试访问变量则ReferenceError。当代码运行到声明变量所在行时,变量被。如果声明中,则变量将被undefined详见MDN或见ES6的let和const声明