1. 程式人生 > >var、let和const的區別詳解

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