揭秘JavaScript变量作用域和提升

每栋建筑物都需要坚实的基础。 了解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();

尝试JS Bin中的示例

既然我们了解了本地和全局范围,以及它们的创建方式,现在该学习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

尝试JS Bin中的示例

我们可以看到,即使使用相同的名称,在执行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)

尝试JS Bin中的示例

这次局部变量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";

尝试JS Bin中的示例

如您所见,当我们记录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");
};

尝试JS Bin中的示例

前面的代码起作用的原因是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

尝试JS Bin中的示例

这次,我们在代码的最后一行中调用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/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值