JavaScript的范围一直是一个棘手的话题,尤其是与更严格组织的语言(例如C和Java)相比时。 多年来,JS语言的范围并未得到特别广泛的讨论,因为该语言根本没有任何会严重影响当前状况的手段。 但是ECMAScript 6中有一些新功能,使开发人员可以更好地控制变量的范围。 现在,浏览器很好地支持了这些功能,大多数开发人员都可以使用它们。 但是,考虑到旧的
var
关键字还没有消失的事实,用于声明变量的新关键字不仅意味着新的机会,而且还意味着新问题的出现。 何时使用
let
和
const
关键字? 他们的行为如何? 在什么情况下
var
关键字仍然有用? 我们今天发布的翻译材料旨在探讨JavaScript中变量范围的问题。

可变范围:概述
变量的范围是编程中的重要概念,但是,这会使一些开发人员,尤其是新手感到困惑。 变量的范围是程序中可以访问此变量的部分。
看下面的例子:
var myVar = 1; function setMyVar() { myVar = 2; } setMyVar(); console.log(myVar);
console.log
方法将输出什么? 这个问题的答案不会让任何人惊讶:它将输出
2
。 变量
myVar
在函数外部声明,这告诉我们它是在全局范围内声明的。 因此,在相同作用域中声明的任何函数都将能够访问
myVar
。 实际上,在浏览器中执行代码时,即使在连接到页面的其他文件中声明的函数也可以访问此变量。
现在看下面的代码:
function setMyVar() { var myVar = 2; } setMyVar(); console.log(myVar);
与前面的示例相比,从表面上看,它的变化微不足道。 即,我们只是将变量声明放入函数中。 现在
console.log
将输出什么? 实际上,什么也没有,因为没有声明此变量,并且在您尝试访问它时,将显示有关未处理的
ReferenceError
错误的消息。 发生这种情况是因为变量是使用
var
关键字在函数内部声明的。 因此,此变量的范围仅限于函数的内部范围。 可以在此函数的主体中对其进行访问,该函数中嵌入的函数可以使用它,但是不能从外部访问它。 如果需要在同一级别使用多个函数来使用某个变量,则需要在声明这些函数的位置声明该变量,即比其内部作用域高一级。
这是一个有趣的发现:大多数网站和Web应用程序的代码不适用于任何一位程序员的工作。 大多数软件项目是团队开发的结果,此外,它们使用第三方库和框架。 即使只有一个程序员参与网站的开发,他通常也会使用外部资源。 因此,通常不建议在全局范围内声明变量,因为您无法事先知道其他变量将由其代码将在项目中使用的其他开发人员声明。 为了解决此问题,尽管将数据和函数封装在普通对象中可以达到相同的效果,但是在将面向对象的方法应用于JavaScript开发时,可以使用一些技巧,特别是“
模块 ”模式和
IIFE 。 通常,应该指出,范围超出其所需范围的变量通常是需要执行某些操作的问题。
Var关键字问题
因此,我们弄清楚了“范围”的概念。 现在,让我们继续进行更复杂的事情。 看下面的代码:
function varTest() { for (var i = 0; i < 3; i++) { console.log(i); } console.log(i); } varTest();
执行控制台后会得到什么? 显然,递增计数器
i
的值:
0
和
2
将显示在循环内部。 循环结束后,程序将继续运行。 现在,我们尝试访问此循环外部
for
循环中声明的相同计数器变量。 这会发生什么?
在循环外调用
i
后,3将进入控制台,因为
var
关键字在功能级别起作用。 如果使用
var
声明变量,那么即使退出声明该变量的构造,也可以在函数中访问它。
当功能变得更加复杂时,这可能会成为一个问题。 考虑以下示例:
function doSomething() { var myVar = 1; if (true) { var myVar = 2; console.log(myVar); } console.log(myVar); } doSomething();
现在将如何使用控制台?
2
和
2
。 我们声明一个变量,用数字1对其进行初始化,然后尝试在
if
内重新定义相同的变量。 由于这两个声明存在于同一作用域中,因此即使我们显然只想这样做,也无法声明具有相同名称的新变量。 结果,第一个变量在
if
内被覆盖。
这恰恰是
var
关键字的最大缺陷。 使用它声明的变量范围太大。 这可能会导致意外的数据覆盖和其他错误。 大范围的可见性通常会导致程序不正确。 通常,变量的范围应受其需求限制,但不能超出需求。 能够声明其作用域不像使用
var
时那样大的变量将是一个很好的选择,这将使它在必要时可以使用更稳定,更防错的软件构造。 实际上,ECMAScript 6给了我们这样的机会。
声明变量的新方法
与
var
相比,ECMAScript 6标准(一组新的JavaScript功能,也称为ES6和ES2015)为我们提供了两种声明范围不同的变量的新方法,这些变量具有更多功能。 这些是
let
和
const
关键字。 两者都给了我们所谓的块作用域。 这意味着它们的使用范围可以限于一段代码,例如
for
循环或
if
。 这使开发人员在选择变量范围时更具灵活性。 考虑新的关键字。
▍使用let关键字
let
关键字与
var
非常相似,主要区别在于用它声明的变量的范围有限。 我们重写上面的示例之一,用
let
替换
var
:
function doSomething() { let myVar = 1; if (true) { let myVar = 2; console.log(myVar); } console.log(myVar); } doSomething();
在这种情况下,数字
2
和
1
将进入控制台。 发生这种情况是因为
if
为使用
let
关键字声明的变量设置了新范围。 这导致以下事实:第二个声明的变量是完全独立的实体,与第一个无关。 您可以彼此独立地与他们合作。 但是,这并不意味着嵌套的代码块(例如我们的
if
)会完全与使用
let
关键字声明的变量在它们本身所在的范围内完全分开。 看下面的代码:
function doSomething() { let myVar = 1; if (true) { console.log(myVar); } } doSomething();
在此示例中,控制台将获得数字
1
。
if
的代码可以访问我们在其外部创建的变量。 因此,它在控制台中显示其值。 如果您尝试混合示波器,会发生什么? 例如,执行以下操作:
function doSomething() { let myVar = 1; if (true) { console.log(myVar); let myVar = 2; console.log(myVar); } } doSomething();
似乎第一个
console.log
调用将输出
1
,但实际上,当您尝试执行此代码时,将出现
ReferenceError
错误,告诉我们该范围的
myVar
变量未定义或未初始化(此错误的文本不同浏览器)。 在JavaScript中,存在将变量提升到其作用域顶部的事情。 也就是说,如果在某个范围内声明了变量,则JavaScript甚至会在执行声明该变量的命令之前为其保留一个位置。 使用
var
和
let
时,发生这种情况的确切方式有所不同。
考虑以下示例:
console.log(varTest); var varTest = 1; console.log(letTest); let letTest = 2;
在这两种情况下,我们都尝试在声明变量之前使用该变量。 但是控制台输出命令的行为有所不同。 第一个使用的变量随后将使用
var
关键字声明,将输出
undefined
-即将写入该变量的内容。 第二个尝试访问变量的命令,该命令稍后将使用
let
关键字进行声明,它将引发
ReferenceError
并告诉我们在变量声明或初始化之前我们正尝试使用该变量。 怎么了
但是事实是,在执行代码之前,负责执行该代码的机制会查看该代码,找出是否在其中声明了任何变量,如果是,则为它们保留空间以提高它们。 在这种情况下,使用
var
关键字声明的变量将在其范围内初始化为
undefined
,即使在声明它们之前已对其进行了访问。 这里的主要问题是,变量中的
undefined
值并不总是表明它们在声明变量之前就试图使用该变量。 看下面的例子:
var var1; console.log(var1); console.log(var2); var var2 = 1;
在这种情况下,尽管
var1
和
var2
声明不同,但是对
console.log
两次调用都将输出
undefined
。 这里的要点是,在使用
var
声明但未初始化的变量中,
undefined
的值
undefined
自动写入。 同时,正如我们已经说过的,使用
var
声明的变量(在声明之前就可以访问)也包含
undefined
。 结果,如果这样的代码出了点问题,就不可能理解错误的根源-使用未初始化的变量或在声明之前使用变量。
使用
let
关键字声明的变量的位置保留在其块中,但是在声明之前,它们会落入临时死区(TDZ,时间死区)中。 这导致以下事实:在声明它们之前不能使用它们,并且尝试访问此类变量会导致错误。 但是,系统确切地知道了问题的原因并报告了问题。 在此示例中可以清楚地看到:
let var1; console.log(var1); console.log(var2); let var2 = 1;
在这里,对
console.log
的第一个调用将输出
undefined
,第二个调用将引发
ReferenceError
错误,告诉我们该变量尚未声明或初始化。
结果,如果使用
var
出现
undefined
,则我们不知道该程序出现此行为的原因。 变量可以被声明和未初始化,或者可能尚未在此范围内声明,但是将在访问它的命令下方的代码中声明。 使用
let
关键字,我们可以了解到底发生了什么,这对于调试非常有用。
▍使用const关键字
const
关键字与
let
非常相似,但是它们有一个重要的区别。 此关键字用于声明常量。 常量的值在初始化后不能更改。 应当指出,这仅适用于原始类型,水,字符串或数字的值。 如果常量是更复杂的东西(例如,对象或数组),则可以修改此类实体的内部结构,您不能仅将其替换为另一个。 看下面的代码:
let mutableVar = 1; const immutableVar = 2; mutableVar = 3; immutableVar = 4;
该代码将执行到最后一行。 尝试将新值分配给常量将导致
TypeError
错误。 这就是常量的行为方式,但是,正如已经提到的那样,可以更改常量初始化的对象,它们可能会发生突变,从而导致
意外 。
也许您作为JavaScript开发人员想知道为什么变量的免疫力很重要。 常量是JavaScript中的一种新现象,而常量则是诸如C或Java这样的语言的基本组成部分。 为什么这个概念如此受欢迎? 事实是,使用常量使我们考虑代码的工作方式。 在某些情况下,更改变量的值可能会破坏代码,例如,如果在其中写入了数字Pi并不断对其进行访问,或者该变量具有指向您需要不断使用的HTML元素的链接。 说,这是一个常量,其中写入到特定按钮的链接:
const myButton = document.querySelector('#my-button');
如果代码取决于HTML元素的链接,那么我们需要确保此链接的不变性。 结果,我们可以说
const
关键字不仅沿着可见性领域的改进之路,而且还沿着限制修改使用此关键字声明的常量值的可能性的途径。 记住我们怎么说变量应该完全具有它需要的范围。 可以通过提出建议来继续这个想法,根据该建议,变量应仅具有更改的能力,这是正确使用该变量所必需的,仅此而已。
这是关于抗扰性主题的很好的材料,从中可以得出一个重要的结论,根据该结论,使用不可变变量可使我们对代码进行更仔细的考虑,从而提高了代码的纯度,并减少了其操作引起的令人不愉快的意外事件数量。
当我第一次开始使用
let
和
const
关键字时,我基本上使用了
let
,仅在将新值写入用
let
声明的变量时才使用
const
可能会损害程序。 但是,在学习了更多有关编程的知识之后,我改变了主意。 现在我的主要工具是
const
,仅在需要重写变量的值时才
let
我使用它。 这使我考虑是否真的有必要更改某个变量的值。 在大多数情况下,这不是必需的。
我们需要var关键字吗?
let
和
const
关键字有助于更负责任的编程方法。 在某些情况下仍需要
var
关键字吗? 是的,有。 在某些情况下,此关键字仍然对我们有用。 在将
var
更改为
let
或
const
之前,请仔细考虑我们将要讨论的内容。
▍浏览器对Var关键字的支持级别
用
var
关键字声明的变量具有
let
和
const
缺少的一项非常重要的功能。 即,我们正在谈论的事实是,绝对所有浏览器都支持此关键字。 尽管浏览器对
let和
const的支持非常好,但是,您的程序仍有可能在不支持它们的浏览器中运行。 为了了解此类事件的后果,您需要考虑浏览器如何处理不受支持的JavaScript代码,而不是例如如何对待不了解的CSS代码。
如果浏览器不支持某些CSS功能,则基本上会导致屏幕上显示内容的某些失真。 浏览器中的网站不支持该网站使用的任何样式,虽然外观看起来并不理想,但很有可能会被使用。 例如,如果使用
let
,并且浏览器不支持此关键字,那么您的JS代码将在那里根本无法使用。 不会-仅此而已。 鉴于JavaScript是现代网络的重要组成部分之一,如果您需要程序在过时的浏览器中运行,这可能成为一个严重的问题。
当人们谈论浏览器对网站的支持时,他们通常会询问该网站将在哪种浏览器中最佳运行。 如果我们谈论的站点的功能基于
let
和
const
的使用,则必须以不同的方式提出类似的问题:“我们的站点在哪些浏览器中不起作用?”。 这比谈论是否使用
display: flex
是否使用
display: flex
)要严重得多。 对于大多数网站,使用过时浏览器的用户数量将不足以令人担忧。 但是,如果我们谈论的是在线商店或所有者购买广告的网站之类的东西,那么这可能是一个非常重要的考虑因素。 在此类项目中使用新机会之前,请评估风险等级。
如果您需要支持真正的旧浏览器,但要使用
let
,
const
和其他新的ES6功能,则解决此问题的方法之一是使用JavaScript传输器,例如
Babel 。 编译器可将新代码翻译成旧浏览器可以理解的内容。 使用Babel,您可以编写使用该语言最新功能的现代代码,然后将其转换为较旧的浏览器可以运行的代码。
, ? , . , , , , . , . , , . ES6-, Babel, Babel , , . , , . . ? - IE8 ? , , , , , .
▍ var
,
var
, . . :
var myVar = 1; function myFunction() { var myVar = 2;
,
myVar
, , . , . , , , , . , .
var
.
var myVar = 1; function myFunction() { var myVar = 2; console.log(myVar);
var
,
window
.
let
const
. , JS- , (, , ) , .
, . , . , , , :
let myGlobalVars = {}; let myVar = 1; myGlobalVars.myVar = myVar; function myFunction() { let myVar = 2; console.log(myVar);
, , , , . , ,
var
, , , , , .
总结
那么该选择什么呢? ? :
- IE10 - ? —
var
. - JavaScript, , ,
var
, const
. - (, , ) — let
.
let
const
, ECMAScript 6, ( ) - -. , , , . , - , «» «» , ,
let
const
, .
! ,
const
var
,
let
, , , ?
