在变量中存储数值是编程中的一个基本概念。一个变量的 "范围 "决定了它在整个程序中何时可用,何时不能用。理解JavaScript中的变量范围是建立坚实语言基础的关键之一。
这篇文章将解释JavaScript的范围系统是如何工作的。你会了解到声明变量的不同方式,局部范围和全局范围的区别,以及一种叫做 "提升 "的东西--一种可以将看似无害的变量声明变成一个微妙的错误的JavaScript怪癖。
变量范围
在JavaScript中,变量的范围是由变量声明的位置控制的,它定义了程序中某个特定变量可以被访问的部分。
目前,有三种方法可以在JavaScript中声明一个变量:使用旧的var 关键字,以及使用新的let 和const 关键字。在ES6之前,使用var 关键字是声明变量的唯一方法,但现在我们可以使用let 和const ,这两个关键字有更严格的规则,使代码不容易出错。我们将在下面探讨这三个关键字之间的区别。
不同语言的作用域规则各不相同。JavaScript有两个作用域:全局和局部。本地范围有两种变化:旧的函数范围,以及ES6引入的新的块范围。值得注意的是,函数作用域实际上是块作用域的一种特殊类型。
全局作用域
在一个脚本中,最外层的作用域是全局作用域。在这个作用域中声明的任何变量都会成为全局变量,可以从程序的任何地方访问。
// Global Scope
const name = "Monique";
function sayHi() {
console.log(`Hi ${name}`);
}
sayHi();
// Hi Monique
正如这个简单的例子所示,变量name 是全局的。它被定义在全局范围内,可以在整个程序中被访问。
但是,尽管这看起来很方便,在JavaScript中不鼓励使用全局变量。例如,这是因为它们有可能被其他脚本或程序中的其他地方所覆盖。
本地范围
任何在一个块内声明的变量都属于该块,并成为局部变量。
JavaScript中的一个函数为使用var,let 和const 声明的变量定义了一个范围。在该函数中声明的任何变量只能从该函数和任何嵌套函数中访问。
一个代码块(if,for, 等等)只为用let 和const 关键字声明的变量定义一个范围。var 关键字被限制在函数范围内,这意味着新的范围只能在函数内部创建。
let 和const 关键字有块作用域,它为任何声明它们的块创建一个新的、本地的作用域。你也可以在JavaScript中定义独立的代码块,它们同样可以划定一个作用域。
{
// standalone block scope
}
函数和块的作用域可以被嵌套。在这种情况下,由于有多个嵌套的作用域,一个变量可以在它自己的作用域内或从内部作用域中访问。但是在它的作用域之外,该变量是不可访问的。
一个简单的例子来帮助直观地了解作用域
为了使事情更清楚,让我们用一个简单的比喻。我们世界上的每个国家都有边界。这些边界内的一切都属于这个国家的范围。每个国家都有很多城市,每个城市都有自己的城市范围。国家和城市就像JavaScript函数或块。它们有自己的局部作用域。大陆的情况也是如此。虽然它们的面积很大,但它们也可以被定义为区域。
另一方面,世界上的海洋不能被定义为有本地范围,因为它们实际上包裹了所有的本地对象--大陆、国家和城市--因此,它们的范围被定义为全球范围。让我们在下一个例子中直观地看到这一点。
var locales = {
europe: function() { // The Europe continent's local scope
var myFriend = "Monique";
var france = function() { // France country's local scope
var paris = function() { // The Paris city's local scope
console.log(myFriend); // output: Monique
};
paris();
};
france();
}
};
locales.europe();
请看笔
变量范围。1 by SitePoint (@SitePoint)
onCodePen.
在这里,myFriend 变量可以从paris 函数中获得,因为它被定义在france 函数的外部范围中。如果我们把myFriend 变量和控制台语句对调,我们会得到ReferenceError: myFriend is not defined ,因为我们不能从外部范围到达内部范围。
现在我们明白了什么是局部和全局作用域,以及它们是如何被创建的,现在是时候学习JavaScript解释器如何使用它们来寻找一个特定的变量。
回到给定的比喻,假设我想找到我的一个朋友,她的名字是Monique。我知道她住在巴黎,所以我从那里开始搜索。当我在巴黎找不到她的时候,我再往上走一级,把搜索范围扩大到整个法国。但同样,她不在那里。接下来,我再次扩大搜索范围,再升一级。最后,我在意大利找到了她,在我们的例子中,意大利是欧洲的本地范围。
在前面的例子中,我的朋友Monique是由变量myFriend 。在最后一行,我们调用了europe() 函数,该函数又调用了france() ,最后当paris() 函数被调用时,搜索开始了。JavaScript解释器从当前执行的范围开始工作,一直到找到相关的变量。如果在任何作用域中没有找到该变量,就会抛出一个异常。
这种类型的查找被称为词法(静态)范围。程序的静态结构决定了变量的范围。变量的作用域由其在源代码中的位置决定,嵌套函数可以访问其外部作用域中声明的变量。无论一个函数从哪里调用,甚至如何调用,其词法范围只取决于函数的声明位置。
现在我们来看看新的块作用域是如何工作的。
function testScope(n) {
if (true) {
const greeting = 'Hello';
let name = n;
console.log(greeting + " " + name); // output: Hello [name]
}
console.log(greeting + " " + name); // output: ReferenceError: greeting is not defined
}
testScope('David');
参见CodePen上的Pen
Variable Scope: 2 by SitePoint (@SitePoint)
。
在这个例子中,我们可以看到,用const 和let 声明的greeting 和name 变量在if 块之外是不可访问的。
现在让我们用var 替换const 和let ,看看会发生什么。
function testScope(n) {
if (true) {
var greeting = 'Hello';
var name = n;
console.log(greeting + " " + name); // output: Hello [name]
}
console.log(greeting + " " + name); // output: Hello [name]
}
testScope('David');
见笔
变量范围。3 by SitePoint (@SitePoint)
onCodePen.
正如你所看到的,当我们使用var 关键字时,变量在整个函数范围内都是可以到达的。
在JavaScript中,同名的变量可以在多层嵌套作用域中被指定。在这种情况下,局部变量比全局变量获得优先权。如果你声明了一个本地变量和一个全局变量的名字相同,那么当你在一个函数或块中使用它时,本地变量将优先。这种类型的行为被称为阴影。简单地说,内部变量对外部变量有阴影。
这正是JavaScript解释器在试图找到一个特定变量时使用的机制。它从当时正在执行的最内层的作用域开始,一直到找到第一个匹配的变量为止,不管外层是否有其他同名的变量。让我们看一个例子。
var test = "I'm global";
function testScope() {
var test = "I'm local";
console.log (test);
}
testScope(); // output: I'm local
console.log(test); // output: I'm global
请看笔
变量范围。4 by SitePoint (@SitePoint)
onCodePen.
即使名称相同,在执行testScope() 函数后,局部变量也不会覆盖全局变量。但情况并非总是如此。让我们来考虑一下这个问题。
var test = "I'm global";
function testScope() {
test = "I'm local";
console.log(test);
}
console.log(test); // output: I'm global
testScope(); // output: I'm local
console.log(test); // output: I'm local (the global variable is reassigned)
见笔者
变量范围。5 by SitePoint (@SitePoint)
onCodePen.
这一次,本地变量test ,覆盖了同名的全局变量。当我们运行testScope() 函数内的代码时,全局变量被重新赋值。如果一个局部变量在没有首先用var 关键字声明的情况下被赋值,它就会变成一个全局变量。为了避免这种不必要的行为,你应该在使用本地变量之前声明它们。任何在函数中用var 关键字声明的变量都是一个局部变量。声明你的变量被认为是最好的做法。
注意:在严格模式下,如果你没有首先声明变量就给变量赋值,那是一个错误。
继续阅读SitePoint上的 "解密JavaScript变量范围和提升"。