var、let和const的區別詳解

  let 和 const 是 ECMAScript6 新推出的特性,其中 let 是能夠替代 var 的「標準」,所以我們探討 var、let 和 const 的區別,首先應該知道 var 到底有什麼不規範的地方,或者是說有什麼弊端。

var 的 特性

變量提升

  var 是 Javascript 用來定義變量的一個關鍵字,這是一個簡單的變量定義方式

var a = 0;

  但是如果我們在定義這個變量之前,查詢這個 a 的話,其實是不會報錯誤的

console.log(a); // undefined
var a = 0;
console.log(a); // 0

  雖然它輸出了 undefined ,但這並不是我們想要的,我們希望在變量初始化前,是無法訪問這個變量的,這雖然是一個約束,但是能讓你的程序變得更可捉摸與維護。而 var 之所以能在var a = 0;前被訪問,是因為這一句話在編譯的時候其實是按以下的順序進行的:

var a;
console.log(a); // undefined
a = 0;
console.log(a); // 0

  這就是為什麼第二行 a 會打印出 undefined 的原因了,這個叫做變量提升,使用 var 初始化變量的時候,在該作用域的開始,會先定義這個變量,再在後面進行賦值(初始化)。所以甚至你可以這麼玩兒:

console.log(a); // undefined
a = 0;
console.log(a); // 0
var a;

  這樣也是可以成功打印的,所以大家看到這應該明白了 var 會對程序眼維護造成的困擾吧? a 明明一路看下來沒有看到的,居然依舊正常使用?!!這個時候還需要從該作用域查看到底有沒有隱藏的var a;如果沒有的話那還需要去作用域外邊尋找。

console.log(a); // undefined
let a = 0;
console.log(a); // 0
var 的作用域

  我們再來看一下以下代碼

for(var i = 0; i < 5; i++){
    var a = 0;
}
console.log(i); // 5
console.log(a); // 0

  我們定義在 for 循環塊中的變量 i、a 居然在循環外都能被取到,這顯然並不規範。

  那麼這裡可以再給大家提一個面試經常也會提到的題目

for (var i = 0; i < 5; i++) {
    setTimeout(function () {
        console.log(i); // 5 5 5 5 5 
    }, 300);
}
for (let j = 0; j < 5; j++) {
    setTimeout(function () {
        console.log(j); // 0 1 2 3 4
    }, 300);
}

  幾乎一模一樣的代碼,就因為var、let的作用域區別,得到的結果大不一樣。對於第一個for循環來說,i在全局範圍內都有效,在setTimeout函數中沒有找到i的定義,所以在全局裡才能找到變量i,在for循環執行完之後,event loop中的,setTimeout中的匿名函數開始執行,這個時候的全局i其實已經是5了,所以全部打印出了5。
  而對於第二個for循環來說,let有塊級作用域的概念,且值得注意的是for循環頭部的let聲明有一個特殊的行為,這個行為指出變量在循環過程中不止被聲明一次,每次迭代都會被聲明。隨後的每個迭代都會使用上一個迭代結束時的值來初始化這個變量。這使得我們的j,每次執行其實都是塊級作用域中的不同的一個變量,自然就不是最終的5了。
  所以如果在不能使用E6的時候,可以使用閉包來創建一個新的作用域。這個閉包能保留對它被聲明的位置所處的作用域的引用,將i強行容留在自執行函數的作用域中使其不被回收。這樣就能獲取到每次的i值。

for (var i = 0; i < 5; i++) {
    (function (i) {
        setTimeout(function () {
            console.log(i) // 0 1 2 3 4
        }, 300)
    })(i)
}
var 可重複定義

  這個很好理解,var是允許定義兩次的,不報錯誤。

var a = 0;
console.log(a); // 0
var a = 1;
console.log(a); // 1

  編輯器會在判斷有已經聲明的同名變量時,會忽略 var 關鍵字,然後直接賦值,所以如果重複使用的一個聲明有一個初始值,那麼它是一個賦值語句。如果重複使用的一個聲明沒有一個初始值,那麼它不會對原來存在的變量有任何的影響。這對於 ES5 之前的 JS 是合法的,但是 ES6 後,認為這種重複定義的做法是不科學的, let 和 const 皆不允許作用域內重複一個定義同名變量。

現在我們來講講 let

暫存死區

  首先,絕對是不允許在let定義變量前使用這個變量的

console.log(a); // ReferenceError: a is not defined
let a = 0;
console.log(a); // 0

  let聲明不會被提升到當前執行上下文的頂部,從該塊級作用域開始,到初始化位置,稱作「暫存死區」,所以在對於a的暫存死區中使用a會報Reference錯誤。

塊級作用域

  let聲明的變量擁有塊級作用域,在塊級作用域外是訪問不到該變量的,而var不同,在for循環中定義的變量可以在外部訪問到,這會導致一些意想不到的bug。注意,大括號 {} 是塊級作用域,不是說函數作用域。

禁止重定義
let a = 0;
console.log(a); // 0
let a = 1;
console.log(a); // SyntaxError: Identifier 'a' has already been declared
window 對象

  在 HTML 中, 全局作用域是針對 window 對象,var關鍵字定義的全局作用域變量屬於 window 對象,而let定義的不屬於。注意這是在瀏覽器環境下,如果是Node則無window對象。

var a = 0;
console.log(window.a) // 0
let b = 1;
console.log(window.b) // undefined

const

  其實const和let非常非常類似,let該有的特性,const都有,不同就是,第一,const不允許「修改」變量,第二const必須初始化,而let不需要

const a = 0;
a = 1; // TypeError: Assignment to constant variable
const b; // SyntaxError: Missing initializer in const declaration

  不過const定義的不是真正意義上的常量,如果定義的是一個對象或者數組,其實是可以變化的,只不過不能將不同的數據類型分配給他,一般可以記:const初始化定義後,不可使用 = 賦值。

const a = {}
a.b = 1;
console.log(a) // { b: 1 }
a = 1 // TypeError: Assignment to constant variable
Tags: