var、let和const应该怎么用?(34)

发布于2019-04-20 11:58:32

在这篇文章中,我们将介绍两种新的在JavaScript(ES6)中创建变量的方式,即使用let和const。同时,我们将探讨var、let和const之间的区别,以及函数与块作用域、变量提升和不变性等内容。

如果你想观看视频,这是链接:https://youtu.be/6vBYfLCE9-Q

ES2015(或ES6)引入了两种创建变量的新方法:let和const。但在我们深入了解var、let和const之间的区别之前,需要先了解一些先决条件。它们是变量声明与初始化、作用域(特别是函数作用域)和变量提升。

变量声明与初始化

变量声明引入了新标识符。

var declaration

我们创建了一个叫作declaration的新标识符。在JavaScript中,刚创建的变量会被初始化为undefined。如果我们在控制台打印变量declaration,将会看到输出undefined。

var declaration 
console.log(declaration)

与变量声明相反,变量初始化是指首次为变量赋值。

var declaration
console.log(declaration) // undefined
declaration = 'This is an initialization'

我将一个字符串赋值给declaration变量,以此来初始化它。

这引出了我们的第二个概念——作用域。

作用域

作用域定义了在程序内部可以访问哪里的变量和函数。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数组,我们需要一个函数,将这个数组和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循环之外还能继续访问i、discountedPrice和finalPrice。它对我们没有任何好处,甚至在某些情况下可能会对我们造成伤害。但因为用var声明的变量的作用域是整个函数,所以你可以这样做。

现在,我们已经讨论完变量声明、初始化和作用域,在深入了解let和const之前,我们需要了解的最后一个东西是变量提升。

变量提升

之前我们说过,“在JavaScript中,刚创建的变量会被初始化为undefined”。事实证明,这就是“变量提升”。JavaScript解释器将在所谓的“创建”阶段为声明的变量分配默认值undefined。

有关变量创建、提升和作用域的更多内容,请参阅:https://tylermcginnis.com/ultimate-guide-to-execution-contexts-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、let和const之间的区别。

var、let和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和finalPrice)将会得到一个很少见的错误。

下一个区别与变量提升有关。之前我们曾说过,变量提升是指“JavaScript解释器会在所谓的创建阶段将声明的变量赋值为默认值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
}

我想不出任何你需要在声明变量之前访问变量的理由,所以,看来抛出一个ReferenceError错误比返回undefined会更好。

事实上,这正是let所做的事情。如果你尝试在声明之前访问使用let声明的变量,你将得到ReferenceError错误,而不是undefined。

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
}

let与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。因为这是在向未来的自己以及未来会阅读你的代码的其他开发者发出信号:不应该修改这些变量。如果它需要被修改(比如在for循环中),你应该使用let。

因此,在会发生变化的变量和不会发生变化的变量之间,剩下的就不多了。这意味着你不应该再使用var。

同时也存在一个不是很流行的观点,就是永远不应该使用const,因为即使你试图表明变量是不可变的,正如我们上面所看到的那样,情况并非完全如此。接受这个意见的开发者总是使用let,除非他们的变量是常量,例如_LOCATION_等。

这里再回顾一下,var是函数作用域的,如果你尝试在声明之前访问用var声明的变量,就会得到undefined。const和let是块作用域的,如果你尝试在声明之前访问用let或const声明的变量,你会得到一个ReferenceError错误。最后,let和const之间的区别在于,一旦你为用const声明的变量赋值,你就不能重新赋值,但如果变量是用let声明的,是可以重新赋值的。

更多内容,请关注前端之巅。

英文原文:https://medium.freecodecamp.org/var-vs-let-vs-const-in-javascript-2954ae48c037