原创

JavaScript 中的 var 、let 、const

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

  1. 变量声明vs初始化
    变量声明引入了一个新的标识符。
    var declaration
    

在上面,我们创建了一个名为declaration的新标识符。在JavaScript中,变量在创建时用undefined值初始化。这意味着如果我们试图记录这个声明变量,我们会得到undefined。

var declaration

console.log(declaration) // undefined

如果我们记录这个声明变量,我们会得到undefined。
在变量声明契约中,变量初始化是指您首先将一个值赋给一个变量。

var declaration

console.log(declaration) // undefined

declaration = 'This is an initialization'

这里我们通过将声明变量赋值给一个字符串来初始化它。
这就引出了第二个概念,范围。

  1. 范围
    作用域定义变量和函数在程序内部可访问的位置。在JavaScript中,有两种作用域——全局作用域和函数作用域。根据官方说明 “如果变量语句出现在FunctionDeclaration中,则在该函数中使用function-local作用域来定义变量。”

这意味着如果你用var创建一个变量,这个变量的作用域是它所创建的函数,并且只能在这个函数或者任何嵌套函数内部访问。

function getDate () {
  var date = new Date()

  return date
}

getDate()
console.log(date) // ❌ Reference Error

在上面,我们试图在声明的函数之外访问一个变量。因为date的作用域是getData函数,所以它只能在getDate内部访问,或者在getDate内部访问任何嵌套函数(如下所示)。

function getDate () {
  var date = new Date()

  function formatDate () {
    return date.toDateString().slice(4) // ✅ 
  }

  return formatDate()
}

getDate()
console.log(date) // ❌ Reference Error

现在让我们看一个更高级的例子。假设我们有一个价格数组,我们需要一个函数接收这个数组和一个折扣,然后返回一个新的折扣价格数组。最终目标可能是这样的。

discountPrices([100, 200, 300], .5) // [50, 100, 150]

实现可能是这样的

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是您所知道的唯一编程语言,您可能不会想到这一点。但是,如果您使用的是另一种编程语言,特别是一种受阻塞作用域限制的编程语言,那么您可能会有点担心这里的情况。它不是真的坏了,只是有点奇怪。没有理由在for循环之外仍然访问i、discountedPrice和finalPrice。它并没有给我们带来任何好处,甚至在某些情况下会给我们带来伤害。但是,由于用var声明的变量是函数作用域的,所以需要这样做。

既然我们已经讨论了变量声明、初始化和范围,那么在深入研究let和const之前,我们需要了解的最后一件事情就是提升。

  1. 提升
    还记得我们前面说过的“在JavaScript中,变量在创建时用undefined值初始化”。原来,这就是所谓的“提升”。在所谓的“创建”阶段,JavaScript解释器将为变量声明分配一个默认值undefined。
    有关创建阶段、提升和作用域的更深入的指南,请参阅“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之间的区别是什么?

  1. var VS let VS const
    首先,让我们比较var和let。var和let之间的主要区别是,let的作用域不是函数作用域,而是块作用域。这意味着用let关键字创建的变量在它创建的“块”中以及任何嵌套的块中都是可用的。当我说“block”时,我的意思是任何被大括号{}包围的东西,比如在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没有定义。这告诉我们,用let声明的变量是块作用域的,而不是函数作用域的。因此,试图访问i(或discountedPrice或finalPrice)在它们声明的“block”之外会给我们一个引用错误,就像我们刚刚看到的那样。

var VS let

var: function scoped

let: block scoped

下一个区别与“提升”有关。之前我们说过提升的定义是“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比返回未定义的默认值更好。事实上,这正是let所做的。如果您试图在变量声明之前使用let来访问它,而不是使用undefined(就像使用var声明变量一样),您将得到一个ReferenceError。

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
}
varlet
var:
函数作用域
在声明变量之前访问该变量时未定义
let:
块作用域
在声明变量之前访问它时发生ReferenceError
  1. 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声明了对象,也不意味着不能更改它的任何属性。它只意味着您不能将其重新分配到新值。

  1. 总结
    现在我们还没有回答的最重要的问题是,应该使用var、let还是const?
    最流行的观点,也是我赞同的观点是,你应该一直使用const,除非你知道变量会改变。
    这样做的原因是,通过使用const,您在向未来的自己以及其他必须阅读您的代码的未来开发人员发出信号,告诉他们这个变量不应该更改。
    如果需要更改(比如在for循环中),应该使用let。
    所以在变化的变量和不变的变量之间,剩下的就不多了。
    这意味着你不应该再使用var。
    现在有一个不受欢迎的观点,尽管它仍然有一些有效性,那就是你永远不应该使用const因为即使你试图表明这个变量是不可变的,就像我们在上面看到的,那也不完全是这样。
    同意这种观点的开发人员总是使用let,除非他们的变量实际上是像LOCATION =…这样的常量。
    总结一下,var是函数作用域的如果你想在实际声明之前用var声明一个变量,你会得到undefined。
    const和let的作用域是被阻塞的,如果您试图在声明之前使用let或const声明的变量,您将得到一个ReferenceError。
    最后,let和const之间的区别是,一旦您为const分配了一个值,您就不能重新分配它,但是使用let,您可以。
var VS let VS const

var: 
  function scoped
  undefined when accessing a variable before it's declared

let: 
  block scoped
  ReferenceError when accessing a variable before it's declared

const:
  block scoped
  ReferenceError when accessing a variable before it's declared
  can't be reassigned
正文到此结束
本文目录