var、let和const应该怎么用?

2019 年 3 月 05 日

var、let和const应该怎么用?

在这篇文章中,我们将介绍两种新的在 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 declarationconsole.log(declaration) // undefineddeclaration = '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


2019 年 3 月 05 日 15:034558
用户头像

发布了 731 篇内容, 共 359.7 次阅读, 收获喜欢 1824 次。

关注

评论

发布
暂无评论
发现更多内容

极客时间架构师培训1期-第1周作业

Kaven

第一周作业一:食堂就餐卡系统设计

登顶计划

极客大学架构师训练营

架构师训练营第一期第一周命题作业

朱磊

极客大学架构师训练营

极客时间架构师培训1期-第1周总结

Kaven

我们需要软件工艺

Bruce Talk

敏捷 随笔 Agile

架构师训练营第一周

子青

训练营第一周作业1

仲夏

架构师训练营第 1 期 命题作业

张建亮

架构师训练营第一周作业 (就餐卡UML图)

springH₂O

用简单而又专业的角度为大家揭秘区块链和比特币

CECBC区块链专委会

比特币 区块链 数字货币

项目滞后,如何让自己的技术快速成长

郎哲158

个人成长 舒适区 熟练工

「架构师训练营第 1 期」-食堂卡管理系统

睡不着摇一摇

极客大学架构师训练营

架构师训练营大作业二

邵帅

极客大学--架构师训练营1期-第一周总结(vaik)

行之

第一周命题作业

崔方剑

极客大学架构师训练营

week1-UML图

张兵

极客大学架构师训练营

区块链将掀开人类的伟大时代

CECBC区块链专委会

区块链 智能合约 价值物联网

中国法定数字货币发展新机遇

CECBC区块链专委会

数字货币 数字经济

训练营第一周作业 2

仲夏

架构师训练营第 1 期-第一周学习总结

Todd-Lee

极客大学架构师训练营

LeetCode题解:94. 二叉树的中序遍历,递归,JavaScript,详细注释

Lee Chen

LeetCode 前端进阶训练营

Spring Cloud 微服务实践 (3) - 服务间的调用

xiaoboey

Spring Cloud 熔断 服务调用 Feign

第一周学习总结

崔方剑

极客大学架构师训练营

电商管理系统之交易子系统设计(一)

长沙造纸农

系统设计 产品经理 系统架构 订单管理 电商平台

RxSwift和RxCocoa入门

teoking

ios swift

架构师训练营第一周作业

木头发芽

第1周 架构方法 浮皮潦草之总结

Pyr0man1ac

架构师训练营第一期第一周学习总结

朱磊

极客大学架构师训练营

你附近的人都有谁,这个功能是怎么实现的?

老胡爱分享

redis geo

架构师训练营第 1 期 - 作业提交

Todd-Lee

极客大学架构师训练营

架构师训练营第 1 期-第一周周总结

郑凯元

极客大学架构师训练营

var、let和const应该怎么用?-InfoQ