When to use var vs let vs const in JavaScript

LJTin發表於2019-01-22

原文

medium.freecodecamp.org/var-vs-let-…

在這篇文章中,你將會在javascript(ES6)中學習兩種新的方式建立變數,let和const。在此過程中,我們將看到var,let和const的不同之處,以及包括諸如函式與塊作用域、變數提升和不變性等主題

ES2015(或者ES6)介紹了兩種新的方式建立變數,let和const。但是在我們真正區分var,let和const不同點之前,你應該先了解一些前提條件。它們是變數宣告vs初始化,作用域(特別是函式作用域),和變數提升

Variable Declaration vs Initialization

變數宣告vs初始化

一個變數宣告介紹了一個新的識別符號

var declaration複製程式碼

上面我們建立了一個新的識別符號叫做宣告。在javascript中,當變數被建立,它們以undefined的值被初始化。這意味著,如果我們嘗試列印宣告的變數,我們將得到undefined

var declaration
console.log(declaration)複製程式碼

所以如果我們列印宣告的變數,我們得到undefined

與變數宣告相反,變數初始化是指當你首次給一個變數賦值

var declaration
console.log(declaration) // undefined
declaration = 'This is an initialization'複製程式碼

所以在這裡我們通過賦值給已經宣告當變數一個字串來初始化它。

這就引導我們第二個概念了,作用域

Scope(作用域)

作用域定義了在你的程式裡,變數和函式在哪裡可以獲取到。在JavaScript中,有兩種型別的作用域--全域性作用域和函式作用域,根據官方的規範,

如果變數語句發生在函式宣告中,則變數在這個函式中通過函式區域性作用域來定義

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

function getDate () {
  var date = new Date()
  return date
}
getDate()
console.log(date) // ❌ Reference Error複製程式碼

上面我們嘗試獲取一個被宣告的函式之外的變數。因為date是作用域於getDate的函式,它只能被getDate它本身或者getDate中的任何巢狀函式所獲取(如下所示)

function getDate(){
    var date = new Date()
    function formatDate(){
        return date.toDateString().slice(4) //可獲取
    }
    return formatDate()
}
getDate()
console.log(date) // Reference Error 引用錯誤複製程式碼

現在讓我們來看一個更高階的例子。假設我們有一個prices的陣列,並且我們需要一個函式,這個函式接收一個像折扣的陣列,並且返回一個折扣後的新陣列。最後的目標可能看起來像這樣:

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迴圈之外,這沒有一個理由仍然能夠訪問到i,discountedPrice,和finalPrice。它不會對我們有任何的好處,並且甚至可能在一定程度上對我們造成傷害。然而,因為用var宣告的變數是函式作用域的,所以你可以這樣做。

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

Hoisting提升

記得早的時候我們說過,‘在JavaScript中,變數被建立時可以用undefined值來初始化。事實證明,這就是“Hoisting”的全部。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的每一件事,讓我們最後討論你為什麼在這的重點:var,let和const的區別在哪裡?

var VS let VS const

首先,讓我們比較var和let。var和let主要的不同點不是函式作用域,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迴圈之外列印i,discountedPrice和finalPrice,因為它們被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複製程式碼

我們得到ReferenceError: i is not defined.這告訴我們的是,用let宣告變數是塊作用域而不是函式作用域。所以嘗試在它們宣告的“塊”之外訪問i(或者discountedPrice or finalPrice)將給我們一個引用錯誤,正如我們剛才所看到的。

var VS let
var: function scoped
let: block scoped複製程式碼

下一個區別與提升相關。之前我們說過,提升的定義是“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 VS let
var: 
  function scoped
  undefined when accessing a variable before it's declared
let: 
  block scoped
  ReferenceError when accessing a variable before it's declared複製程式碼

let VS const

現在你理解了var和let的區別,那麼const呢?事實上,const更像是let,然而,唯一的不同點就是你一旦用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宣告,也不意味著你不能改變它的屬性。這僅僅意味你不能重新分配一個新值

現在我們還沒有回答最重要的問題是:你應該使用var,let或者const?最受歡迎的觀點和我表述的觀點是,你應該總是使用const,除非你知道變數將發生改變。這麼做的原因是使用const,你向未來的自己以及必須閱讀你程式碼的任何未來的開發者們發出訊號,這個變數不應該改變。如果它將需要改變(像在for迴圈中),你應該使用let

所以,在可變的變數與不可改變的變數之間,就沒有太多的東西剩下。這意味著你不應該再使用var

總結

var是函式作用域,並且如果你嘗試在實際宣告之前使用一個var宣告的變數,你將得到undefined。

const和let是塊作用域,並且如果你嘗試在實際宣告之前使用let或const宣告的變數,你將得到一個引用錯誤。

最後在let和const之間的區別是一旦你用const分配了一個值,你將不行重新賦值,但是用let可以





相關文章