1. 程式人生 > 實用技巧 >var,let,const三種宣告方式的區別

var,let,const三種宣告方式的區別

變數提升

很多人認為,var是存在變數提升了,因此,如果你有如下程式碼:

console.log(foo);      // undefined
var foo = 123;

程式不會報錯,而是打印出undefined。這是因為,編譯器預編譯的時候,第一步只會記錄變數和函式的定義,第二步才會執行程式(hoisting是對一種現象的描述,而不是一種編譯器具體的行為),所以程式看起來等價於下面的程式碼:

var foo;
console.log(foo);      // undefined
foo = 123;

再來看let是否存在變數提升:

console.log(foo);      //
Uncaught ReferenceError: Cannot access 'foo' before initialization let foo = 123;

發生了引用錯誤,但是這是因為let定義的變數不存在提升導致的麼?

作為對照,我們直接執行下面的程式碼,看看會出現什麼錯誤:

console.log(foo);      //  Uncaught ReferenceError: foo is not defined

錯誤原因:foo沒有定義。

如果說let foo不存在變數提升,那麼在執行

console.log(foo);     
let foo = 123;

也應該報出foo沒有定義這個錯誤的,但是錯誤確是因為foo沒有被初始化。

下面是揭示真相的時刻了:let和const定義的變數都會被提升,但是不會被初始化,不能被引用,不會像var定義的變數那樣,初始值為undefined。因此,在使用console.log(foo)的時候會發生引用錯誤。

為什麼let和const定義的變數不會被初始化呢?主要是因為const。const,顧名思義:常量,const的引用不應被改變。如果編譯器把const初始化為undefined,之後,又讓它等於我們定義的那個值,就改變了const的引用。因此,委員會決定let和const雖然也會發生變數提升,但是沒有任何初始值。

塊級作用域

var、let和const另外一個重要區別就是let和const只在塊級作用域中有效,比如:

{
    var a = 'Smiley';
}
console.log(a)      // 能正確打印出Smiley

但是:

{
    let a = 'Smiley';
}
console.log(a)        //  報錯: Uncaught ReferenceError: a is not defined

迴圈中i的定義

我們來看紅寶書當中的一個例子:

var result = new Array();

for (var i=0; i<10; i++) {
  result[i] = function() {
    return i;
  }
}
console.log(result[0]())       // 列印10

之所以結果返回10,這是因為所有的i都使用同一個引用,而i最終會被更新為10,因此,result中所有函式都會返回10。

那麼怎麼修復呢?在ES6之前,最常見的修復方法是使用IIFE。但是let和const的發明讓我們有了一種更簡單的修復方法:

var result = new Array();

for (let i=0; i<10; i++) {
  result[i] = function() {
    return i;
  }
}
console.log(result[0]());    // 列印0

在每次迴圈當中,都會建立一個新的let i(因為let只在塊級作用域有效,因此是不會報錯的),因此result[i]使用的每個i都是不同的,值並不會像使用var的例子那樣不斷增加。

其他

關於var和let還有一些微小的區別,比如:

  1. 暫時性死區(如果理解了JS的兩步預編譯,這個很容易理解)
  2. let和const不允許重複宣告
  3. let和const不會繫結全域性作用域

這些都比較容易理解,不再贅述了。

let和const之間的區別

另外一點值得注意的是,let和const之間的區別。

大家一般認為const是常量,不能被更改。但是請看下面這段程式碼:

const obj = {
    a: 12
}
obj.a = 1;

我們更改了obj這個const的a這個屬性,但是沒有任何報錯。不是說const定義的變數不能被更改麼?其實是const定義的變數的引用不能被更改,在上面的程式中,如果你說obj = ‘abc’,程式是會報錯的。但是上面例子中,引用其實是一個指標,即使指標指向的東西改變了,但只要指標沒有改變,就不會報錯的。所以,建議只使用const定義簡單型別的資料,定義物件還是使用let會不容易產生歧義。