每栋建筑物都需要坚实的基础。 了解JavaScript中的变量范围是建立坚实基础的关键之一。 本文将解释JavaScript的作用域系统如何工作。 我们还将介绍一个相关的主题,称为提升 。
可变范围
为了有效地使用JavaScript,您需要了解的第一件事是变量作用域的概念。 变量的范围由变量声明的位置控制,并定义程序中可访问特定变量的部分。
范围规则因语言而异。 JavaScript有两个作用域- 全局和局部 。 在函数外部声明的任何变量都属于全局范围,因此可以从代码中的任何位置访问。 每个函数都有自己的作用域,并且只能从该函数和任何嵌套函数访问该函数内声明的任何变量。 由于JavaScript中的局部作用域是由函数创建的,因此也称为函数作用域。 当我们将一个函数放到另一个函数中时,就会创建嵌套作用域。
当前,与许多其他语言不同,JavaScript不支持块级作用域。 这意味着在像for
循环这样的块结构内部声明变量,并不会将变量限制为循环。 而是可以从整个函数访问该变量。 值得注意的是,即将发布的ECMAScript 6将通过let
关键字支持块级范围 。
为了清楚起见,让我们使用一个简单的隐喻。 我们世界上每个国家都有边疆。 这些边界内的一切都属于该国的范围。 每个国家都有许多城市,每个城市都有自己的城市范围。 国家和城市就像JavaScript函数一样-它们都有其本地范围。 各大洲也是如此。 尽管它们规模巨大,但它们也可以定义为语言环境。 另一方面,不能将世界海洋定义为具有局部范围,因为它实际上包裹了所有局部对象-大洲,国家和城市-因此,其范围被定义为全球性。 让我们在下一个示例中对此进行可视化:
var locales = {
europe: function() { // The Europe continent's local scope
var myFriend = "Monique";
var france = function() { // The France country's local scope
var paris = function() { // The Paris city's local scope
console.log(myFriend);
};
paris();
};
france();
}
};
locales.europe();
既然我们了解了本地和全局范围,以及它们的创建方式,现在该学习JavaScript解释器如何使用它们来查找特定变量。
回到给定的隐喻,假设我想找到我的一个朋友,名字叫莫妮克。 我知道她住在巴黎,所以我从那里开始搜寻。 当我在巴黎找不到她时,我会上一层楼,将搜索范围扩大到整个法国。 但是,她又不在那儿。 接下来,我将搜索范围又上了一层,再次扩大了搜索范围。 最终,我在意大利找到了她,在我们的情况下是欧洲的本地范围。
在前面的示例中,我的朋友Monique由变量myFriend
表示。 在最后一行中,我们调用了europe()
函数,该函数调用了france()
,最后,当调用paris()
函数时,搜索开始。 JavaScript解释器从当前执行的作用域开始工作,并逐步解决,直到找到有问题的变量为止。 如果在任何范围内都找不到该变量,则将引发异常。
这种类型的查找称为词汇(静态)范围 。 程序的静态结构确定变量范围。 变量的范围由其在源代码中的位置定义,并且嵌套函数可以访问在其外部范围中声明的变量。 无论从何处调用函数,甚至从何处调用函数,其词法作用域仅取决于函数的声明位置。
在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
我们可以看到,即使使用相同的名称,在执行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)
这次局部变量test
覆盖具有相同名称的全局变量。 当我们在testScope()
函数中运行代码时,将重新分配全局变量。 如果在未先使用var
关键字声明的情况下分配了局部变量,则它将成为全局变量。 为避免此类不良行为,您应始终在使用局部变量之前声明它们。 在函数内部用var
关键字声明的任何变量都是局部变量。 声明变量是最佳实践。
注–在严格模式下 ,如果在未先声明变量的情况下将值分配给变量,则会出错。
吊装
JavaScript解释器在后台执行许多操作,其中之一称为提升。 如果您不了解这种“隐藏”行为,则可能引起很多混乱。 考虑JavaScript变量行为的最佳方法是始终将它们可视化为两个部分:声明和赋值:
var state; // variable declaration
state = "ready"; // variable definition (assignment)
var state = "ready"; // declaration plus definition
在上面的代码中,我们首先声明变量state
,然后为它赋值“ ready”。 在代码的最后一行中,我们看到可以将这两个步骤结合起来。 但是,您需要记住的是,即使它们看起来像是一条语句,实际上,JavaScript引擎也会将该单个语句视为两个单独的语句,就像示例中的前两行一样。
我们已经知道范围内声明的任何变量都属于该范围。 但是我们还不知道的是,无论在特定范围内在何处声明变量,所有变量声明都将移至其范围的顶部(全局或局部)。 这称为提升 ,因为变量声明被提升到作用域的顶部。 请注意,吊装只会移动声明。 任何作业都留在原地。 让我们来看一个例子:
console.log(state); // output: undefined
var state = "ready";
如您所见,当我们记录state
的值时,输出是undefined
,因为我们在实际赋值之前引用了它。 您可能已经预计会引发ReferenceError,因为尚未声明state
。 但是您不知道变量是在后台声明的。 这是JavaScript引擎解释代码的方式:
var state; // moved to the top
console.log(state);
state = "ready"; // left in place
吊装也会影响函数声明。 但是在看到一些示例之前,让我们首先了解函数声明和函数表达式之间的区别。
function showState() {} // function declaration
var showState = function() {}; // function expression
将函数声明与函数表达式区分开的最简单方法是检查单词function
在语句中的位置。 如果function
是语句中的第一件事,那么它就是函数声明。 否则,它是一个函数表达式。
函数声明被完全提升。 这意味着整个功能的主体将移到顶部。 这使您可以在声明函数之前调用它:
showState(); // output: Ready
function showState() {
console.log("Ready");
}
var showState = function() {
console.log("Idle");
};
前面的代码起作用的原因是JavaScript引擎将showState()
函数的声明及其所有内容移到范围的开头。 代码的解释如下:
function showState() { // moved to the top (function declaration)
console.log("Ready");
}
var showState; // moved to the top (variable declaration)
showState();
showState = function() { // left in place (variable assignment)
console.log("Idle");
};
您可能已经注意到,仅悬挂了函数声明,而没有悬挂函数表达式。 将函数分配给变量时,规则与变量提升的规则相同(仅移动声明,而将赋值保留在原处)。
在上面的代码中,我们看到函数声明优先于变量声明。 在下一个示例中,我们将看到当我们进行函数声明与变量赋值时,最后一个具有优先权。
var showState = function() {
console.log("Idle");
};
function showState() {
console.log("Ready");
}
showState(); // output: Idle
这次,我们在代码的最后一行中调用showState()
函数,该函数将改变情况。 现在我们得到输出“ Idle”。 这是由JavaScript引擎解释时的外观:
function showState(){ // moved to the top (function declaration)
console.log("Ready");
}
var showState; // moved to the top (variable declaration)
showState = function(){ // left in place (variable assignment)
console.log("Idle");
};
showState();
要记住的事情
- 在执行代码的任何部分之前,所有声明(函数和变量)都将提升到包含范围的顶部。
- 首先悬挂函数,然后悬挂变量。
- 函数声明的优先级高于变量声明,但优先于变量分配。
From: https://www.sitepoint.com/demystifying-javascript-variable-scope-hoisting/