[譯] 在JavaScript中何時使用var、let及const

十一月de風發表於2019-01-12

在這篇文章中,你將學習兩種在JavaScript(ES6)中建立變數的新方法,let和const。 在此過程中,我們將研究var,let和const之間的差異,以及函式與塊級作用域,變數提升和不變性等主題。

如果你更喜歡觀看視訊的方式,可以看這個:

var vs let vs const: Variable declarations in ES6 | ES2015

ES2015(或ES6)引入了兩種建立變數的新方法,let和const。但在我們真正深入研究var,let和const之間的差異之前,首先需要了解一些先決條件。 它們是變數宣告與初始化,作用域(特別是函式作用域)和提升。

變數宣告與初始化

變數宣告引入了新的識別符號。

var declaration
複製程式碼

上面我們建立了一個名為declaration的新的識別符號。在JavaScript中,變數在建立時使用undefined值初始化。這意味著如果我們嘗試列印declaration變數,我們將得到undefineed。

var declaration

console.log(declaration)
複製程式碼

所以如果我們列印declaration變數,得到undefined.

與變數宣告相反,變數初始化是指首次為變數賦值。

var declaration

console.log(declaration) // undefined

declaration = 'This is an initialization'

複製程式碼

所以這裡我們通過將變數賦值為一個字串來初始化它。

這引出了我們的第二個概念,作用域。

作用域

作用域定義了在你的程式中可以訪問變數和函式的位置。在JavaScript中,有兩種作用域 - 全域性作用域和函式作用域。根據官方規範,

如果變數語句出現在函式宣告中,那麼這個變數在該函式中作為函式區域性作用域被定義。

這就意味著如果你使用var建立了一個變數, 那麼該變數的作用域為它被建立時所在的函式,並且只能在該函式或任何巢狀函式內部訪問。

function getDate () {
  var date = new Date()
  return date
}
getDate()
console.log(date) // ❌ Reference Error

複製程式碼

現在讓我們看一個更高階的例子。假設我們有一個陣列prices,並且我們需要一個函式,接受該陣列以及一個discount作為引數,並返回一個新的折扣後的價格陣列。最終目標可能如下所示:

discountPrices([100, 200, 300], .5)
複製程式碼

並且實現可能像這樣:

function discountPrices (prices, discount) {
  var discounted = []
  for (var i = 0; i < prices.length; i++) {
    var discountedPrice = prices[i] * (1 - discount)
    var finalPrice = Math.round(discountedPrice * 100) / 100
    discounted.push(finalPrice)
  }
  return discounted
}
複製程式碼

看起來很簡單,但這與作用域有什麼關係?看一下for迴圈。在其內部宣告的變數是否可以在其外部訪問?事實證明,他們可以。

function discountPrices (prices, discount) {
  var discounted = []
  for (var i = 0; i < prices.length; i++) {
    var discountedPrice = prices[i] * (1 - discount)
    var finalPrice = Math.round(discountedPrice * 100) / 100
    discounted.push(finalPrice)
  }
  console.log(i) // 3
  console.log(discountedPrice) // 150
  console.log(finalPrice) // 150
  return discounted
}
複製程式碼

如果JavaScript是你知道的唯一的程式語言,你可能不會想到這一點。但是,如果你從另一種程式語言(特別是一種塊級作用域的程式語言)開始使用JavaScript,那麼你可能會對這裡發生的事情感到擔憂。

它並沒有真正破碎,它只是有點奇怪。在for迴圈之外,沒有理由仍然可以訪問idiscountedPricefinalPrice。它對我們沒有任何好處,甚至可能在某些情況下對我們造成傷害。但是,由於用var宣告的變數是函式作用域內的,所以你可以這樣做。

現在我們討論了變數宣告,初始化和作用域, 那麼在我們深入瞭解letconst之前我們需要清除的最後一件事就是變數提升。

變數提升

記得早些時候我們說過“在JavaScript中,變數在建立時用undefined值來初始化。”事實證明,這就是變數提升的全部內容。JavaScript直譯器將在所謂的“建立”階段為變數宣告分配預設值undefined

有關建立階段,變數提升和範圍的更深入的指南,請參閱The Ultimate Guide to Hoisting, Scopes, and Closures in JavaScript

我們來看看前面的例子並且研究一下變數提升是怎麼影響它的。

function discountPrices (prices, discount) {
  var discounted = undefined
  var i = undefined
  var discountedPrice = undefined
  var finalPrice = undefined
  discounted = []
  for (var i = 0; i < prices.length; i++) {
    discountedPrice = prices[i] * (1 - discount)
    finalPrice = Math.round(discountedPrice * 100) / 100
    discounted.push(finalPrice)
  }
  console.log(i) // 3
  console.log(discountedPrice) // 150
  console.log(finalPrice) // 150
  return discounted
}
複製程式碼

請注意,所有變數宣告都分配了預設值undefined。這就是為什麼如果你在實際宣告之前嘗試訪問其中一個變數,你就會得到undefined

function discountPrices (prices, discount) {
  console.log(discounted) // undefined
  var discounted = []
  for (var i = 0; i < prices.length; i++) {
    var discountedPrice = prices[i] * (1 - discount)
    var finalPrice = Math.round(discountedPrice * 100) / 100
    discounted.push(finalPrice)
  }
  console.log(i) // 3
  console.log(discountedPrice) // 150
  console.log(finalPrice) // 150
  return discounted
}
複製程式碼

現在你已經瞭解了有關var的所有內容,讓我們最後談談你真正關心的話題:varletconst之間的區別是什麼?

var 與 let 與 const

首先,我們比較一下varletvarlet的主要區別在於,let作用域是塊級的,而不是函式級別的。這意味著使用let關鍵字建立的變數在建立它的“塊”內以及任何巢狀塊中都可用。當我說“塊”時,我指的是在for迴圈或if語句中用花括號{}包圍的任何東西。

讓我們最後一次回顧我們的discountPrices函式。

function discountPrices (prices, discount) {
  var discounted = []
  for (var i = 0; i < prices.length; i++) {
    var discountedPrice = prices[i] * (1 - discount)
    var finalPrice = Math.round(discountedPrice * 100) / 100
    discounted.push(finalPrice)
  }
  console.log(i) // 3
  console.log(discountedPrice) // 150
  console.log(finalPrice) // 150
  return discounted
}
複製程式碼

記住,我們能夠在for迴圈之外列印idiscountedPricefinalPrice,因為它們是用var宣告的,而var是作用域是在函式內。但是現在,如果我們將var宣告更改為使用let,並嘗試執行,會發生什麼?

function discountPrices (prices, discount) {
  let discounted = []
  for (let i = 0; i < prices.length; i++) {
    let discountedPrice = prices[i] * (1 - discount)
    let finalPrice = Math.round(discountedPrice * 100) / 100
    discounted.push(finalPrice)
  }
  console.log(i) // 3
  console.log(discountedPrice) // 150
  console.log(finalPrice) // 150
  return discounted
}
discountPrices([100, 200, 300], .5) // ❌ ReferenceError: i is not defined

複製程式碼

:no_good: 我們得到ReferenceError:i is not defined。這告訴我們的是,使用let宣告的變數是作用於塊級作用域的,而不是函式作用域。因此,嘗試訪問我們宣告的“塊”之外的i(或discountedPricefinalPrice)會給我們一個引用錯誤,就像我們剛剛看到的那樣。

var VS let

var: 作用域是函式範圍的

let: 作用域是塊級範圍的
複製程式碼

下一個區別與變數提升有關。之前我們說過,變數提升的定義是“JavaScript直譯器會在所謂的'建立'階段將變數賦值為undefined這個預設值。”我們甚至在變數宣告之前通過列印這個變數看到了這一點(你得到了undefined)。

function discountPrices (prices, discount) {
  console.log(discounted) // undefined
  var discounted = []
  for (var i = 0; i < prices.length; i++) {
    var discountedPrice = prices[i] * (1 - discount)
    var finalPrice = Math.round(discountedPrice * 100) / 100
    discounted.push(finalPrice)
  }
  console.log(i) // 3
  console.log(discountedPrice) // 150
  console.log(finalPrice) // 150
  return discounted
}

複製程式碼

我想不出任何你會想要在宣告變數之前訪問它的用例。看起來丟擲一個引用錯誤比返回undefined更好。

實際上,這就是let所做的。如果你試圖在被宣告之前去訪問一個用let宣告的變數,你會得到一個引用錯誤,而不是undefined。(比如使用var宣告的那些變數)

function discountPrices (prices, discount) {
  console.log(discounted) // ❌ ReferenceError
  let discounted = []
  for (let i = 0; i < prices.length; i++) {
    let discountedPrice = prices[i] * (1 - discount)
    let finalPrice = Math.round(discountedPrice * 100) / 100
    discounted.push(finalPrice)
  }
  console.log(i) // 3
  console.log(discountedPrice) // 150
  console.log(finalPrice) // 150
  return discounted
}
複製程式碼
var 與 let

var:
  函式範圍的
  在變數宣告之前使用時得到undefined
  
let:
  作用範圍是塊級的
  在變數宣告之前使用時報引用錯誤
 
複製程式碼

let與const

既然你理解了varlet之間的區別,那麼const呢?事實證明,constlet幾乎完全相同。但是,唯一的區別是,一旦使用const為變數賦值,就無法將其重新賦值給新值。

let name = 'Tyler'
const handle = 'tylermcginnis'
name = 'Tyler McGinnis' // ✅
handle = '@tylermcginnis' // ❌ TypeError: Assignment to constant variable.
複製程式碼

上面的內容是用let宣告的變數可以重新賦值,但用const宣告的變數不能。

很酷,所以只要你想讓一個變數是不可變的,你可以用const宣告它。嗯,也不完全是這樣。因為用const宣告變數並不意味著它是不可變的。它意味著不能重新賦值。這是一個很好的例子。

const person = {
  name: 'Kim Kardashian'
}
person.name = 'Kim Kardashian West' // ✅
person = {} // ❌ Assignment to constant variable.
複製程式碼

請注意,更改物件上的屬性並不是重新賦值,因此即使使用const宣告物件,也不意味著你不能改變其任何屬性。它只表示您無法將其重新分配給新值。

現在我們還沒有回答的最重要的問題是:你應該使用varlet還是const? 最流行的觀點和我贊同的觀點是,除非你知道變數會發生變化,否則你應該總是使用const。 這樣做的原因是使用const,你向未來的自己以及任何其他未來的開發人員發出訊號,這些開發人員必須閱讀你的程式碼,這個變數不應該改變。如果它需要更改(比如在for迴圈中),你應該使用let

因此,在變化的變數和不變的變數之間,沒有多少剩餘。這意味著你不應該再使用var

現在不受歡迎的觀點,雖然它仍然有一些有效性,是你永遠不應該使用const,因為即使你試圖表明變數是不可變的,正如我們上面所看到的那樣,情況並非完全如此。贊成此意見的開發人員總是使用let,除非他們的變數實際上是常量,如_LOCATION_ = ....

所以回顧一下,var是作用於函式範圍的,如果你嘗試在實際宣告之前使用一個用var宣告的變數,你就會得到undefinedconstlet是作用於塊級範圍的,如果你嘗試在宣告之前使用letconst宣告的變數,你會得到一個引用錯誤。最後,letconst之間的區別在於,一旦你為const賦值,你就不能重新為它賦值,但是你可以使用let來做到重新賦值。

var 與 let 與 const

var: 
  函式範圍的
  在變數宣告之前使用時會得到undefined
  
let: 
  作用範圍是塊級的
  在變數宣告之前使用時會報引用錯誤
  
const:
  作用範圍是塊級的
  在變數宣告之前使用時會報引用錯誤
  不能重新賦值
複製程式碼

這篇文章最初釋出於tylermcginnis.com,是其 Modern JavaScript課程的一部分。

相關文章