概念
從概念的字面意義上說,“變量提升”意味著變量和函數(shù)的聲明會在物理層面移動到代碼的最前面,但這么說并不準確。實際上變量和函數(shù)聲明在代碼里的位置是不會動的,而是在編譯階段被放入內存中。
變量的變量提升
var聲明的變量提升
先看個例子
console.log(a) //輸出: undefined
var a = 1 //創(chuàng)建 + 初始化 + 賦值
首先我們知道var 聲明存在「創(chuàng)建、初始化和賦值」三個過程
- 創(chuàng)建: 在環(huán)境中創(chuàng)建了變量
a - 初始化: 將變量a的值初始化為
undefined - 賦值: 將變量
a的值賦值為1
上述例子輸出為undefined,也就是說var聲明的變量「創(chuàng)建、初始化」的過程被提升了,而賦值過程未被提升.
let聲明的變量提升
console.log(a) //報錯: Cannot access 'a' before initialization
let a = 1 //創(chuàng)建 + 初始化
在上述例子中報錯,不能在 a 初始化之前訪問 a,那么說明用 let聲明的變量「初始化」過程未被提升,同時「賦值」過程也便不存在了,那么我們又如何知道「創(chuàng)建」過程有沒有被提升
let a = 1
let b = 1
let c = 1
{
let c = 2
console.log(a) // 報錯: Cannot access 'a' before initialization
console.log(b)// 輸出: 1
console.log(c)// 輸出: 2
let a = 2
}
上述例子中:
- 變量
b在全局作用域中聲明賦值,輸出 1 - 變量
c在塊級作用域中被重新聲明賦值, 輸出 2 - 變量
a在全局作用域中聲明賦值,在塊級作用域中也重新聲明賦值了,此時如果塊級作用域中變量a的「創(chuàng)建」未被提升,那么變量a應當輸出的是全局作用域賦值的值 1. -
值得注意的一點:執(zhí)行
let a = 1是將 變量a初始化為 1,與var的初始化是不同的,而let a才是將變量a初始化為undefined -
const:
const和let只有一個區(qū)別,那就是 const 只有「創(chuàng)建」和「初始化」,沒有「賦值」過程。
函數(shù)的變量提升
首先我們知道函數(shù)的聲明有倆種方式:
- 函數(shù)聲明式
console.log(fn)
function fn () {
console.log(1)
}
// 輸出:
// ? fn () {
// console.log(1)
// }
- 函數(shù)字面量式
console.log(fn)
let fn = function () {
console.log(1)
}
// 輸出:
// undefined
// 若用 let 來聲明函數(shù), 會報錯. 和上述變量提升相同.
總結:
-
var的「創(chuàng)建」和「初始化」都被提升了。 -
let的「創(chuàng)建」過程被提升了,但是初始化沒有提升。 -
function的「創(chuàng)建」「初始化」和「賦值」都被提升了。 - 同一個變量只會聲明一次,其他的會被忽略掉或者覆蓋掉。
- 函數(shù)聲明的優(yōu)先級高于變量聲明的優(yōu)先級,并且函數(shù)聲明和函數(shù)定義的部分一起被提升。
題目
- 下列代碼輸出的是什么,為什么?
console.log(a);
var a = 1;
function foo() {
console.log(a);
var a = 2;
console.log(a);
}
foo();
console.log(a);
// undefined undefined 2 1
- 如何理解
let x = x報錯之后,再次let x依然會報錯?
let x = x
// 報錯:Cannot access 'x' before initialization
let x = 1
// 報錯:Identifier 'x' has already been declared
1.因為let x = x是將變量x初始化為x,而 x還未被初始化,所以初始化失敗.
2.x無法再被初始化, 是因為 x 已經(jīng)在環(huán)境中被創(chuàng)建.
3.即x像是處在創(chuàng)建完成到初始化過程中間,一種類似鎖定的狀態(tài).