【问题标题】:What is the precise definition of variable scope (in JavaScript)?变量范围的精确定义是什么(在 JavaScript 中)?
【发布时间】:2021-02-10 12:50:02
【问题描述】:

我想确保掌握变量作用域的概念。理想情况下,我想要一个表单的定义:

  • x 是标识符 i 范围,以防万一 x ...

或形式:

  • 标识符i范围等同于...

虽然我是编程新手,但我已经阅读了一些关于变量作用域的 general material 和一些关于 JavaScript 中变量作用域的材料(包括 this)。但是,我似乎找不到一个精确的、描述性的定义。我试图想出自己的想法,但我注意到它并不总是适合其他人的说法。

最好能说明我目前的理解。让我们将“上下文”几乎作为一个原语来表示类似由花括号分隔的代码区域。想想代码、函数和块的主要区域(我敢打赌我已经遗漏了一些东西)。让我们从广义上考虑变量,包括变量、参数和函数名称或标识符,更一般地说。那么:

  • 变量v 的范围与v 可访问的上下文集相同。

这是一个例子:

let a = 'foo';
function bar(param) {
  param += 2;  
  function newFunc() {
   console.log(param);
  }  
  newFunc(); 
}

这里我们有三个上下文:

  • 主要背景。
  • 定义bar 函数产生的上下文。
  • 定义 newFunc 函数产生的上下文。

根据上面的定义尝试,我们有两个作用域:

  • 有像consolenull等内置名称的全局范围,以及定义的名称abar。此范围是上述上下文 1、2 和 3 的集合。
  • paramnewFunc的范围。此范围是上述上下文 2 和 3 的集合。

没有范围只包含上下文 3,因为在上下文 3 中没有定义新变量。

因此,定义一个函数会创建一个上下文。变量的范围包括新的上下文,以防万一变量可以从该上下文中访问。

但是,我知道人们会说上面的代码示例包含 3 个作用域,并且或多或少地用我所说的“上下文”来标识这些作用域。但是,我无法理解这一点,并且我不知道变量范围的精确定义允许他们说上面有 3 个范围。在不谈论变量/标识符的范围的情况下谈论范围对我来说似乎很奇怪。更准确地说,似乎上下文无疑是独立于变量的,但范围不是。范围是变量/标识符的属性;这就是为什么我们在谈论作用域时谈论变量作用域或某个标识符的作用域。

我还看到人们谈论变量进出范围。对我来说,变量有一个范围,周期。程序从上下文移动到上下文,从而进出变量的范围。变量在程序运行时进出上下文。它们甚至可能总是在上下文中(如果它们具有全局作用域)或总是脱离上下文(例如,如果它们具有函数作用域并且该函数从未被调用)。但说变量进出范围或变量的范围发生变化似乎不合适。

我突然想到,除非存在可以访问标识符的唯一上下文,否则谈论标识符的 范围是不正确的。所以,相反,我们可能有

  • S 是标识符va 范围,以防S 是可以访问v 的上下文。

根据这个定义,我们有三个作用域:

  • 上下文 1 是 abar 的范围。
  • 上下文 2 是 abarparamnewFunc 的范围。
  • 上下文 3 是 abarparamnewFunc 的范围。

在上面的代码中,没有任何上下文确实是标识符的 范围。在这种情况下,必须有一个唯一的上下文可以访问标识符。例如,如果代码省略了newFun,则不会有第三个上下文,而上下文2 将是paramnewFunc 范围。

因此,定义一个函数会创建一个上下文。上下文是变量的范围,以防万一该变量可在该新上下文中访问。

不过,这里也有问题。考虑以下代码:

for (let i = 0; i < 5; i += 1) {
  if (i % 2 === 0) {
    console.log(i);
  }
}

我看到有人说这里有三个上下文,但是 if 块的内部嵌套之一不是范围。但是根据我刚刚给出的定义,该块将是i 的范围以及像console 这样的内置函数。

这就是我目前所在的位置。提前感谢您的帮助。

更新

给定@A。 Chiesa 的回应,我写了一个short thing 来尝试理解事情。也许那个文件说得最好?再次感谢。

【问题讨论】:

  • "在不讨论变量/标识符的范围的情况下谈论范围对我来说似乎很奇怪。" 定义范围的不是变量。您可以拥有一个没有变量的功能范围。它仍然是一个有效的范围,但它是空的。
  • 语言和术语是流动的。变量有范围;有人说它“超出范围”,他们只是意味着它在当前上下文中不存在。不管里面是否有任何“本地”的东西,上下文都存在——即使函数没有定义局部变量,它们也肯定有一个上下文;这就是允许闭包存在的原因。
  • 我要补充一点,现阶段可能不值得这么多字,而且没有适合每个开发人员或每种语言的单一词汇。

标签: javascript scope


【解决方案1】:

免责声明:以下是我自己的话的解释。我对这些概念很有信心, 但对特定术语持保留态度:可能是规范或其他开发人员使用了不同的术语。

在 JavaScript 中,人们倾向于引用的“范围”是所谓的 函数的“词法作用域”。

恕我直言,最好不要从定义的变量(或常量)的角度考虑范围,而是从每行代码的 PoV 考虑。

例如:

let a = 'foo';

function bar(param) {
  param += 2;  
  function newFunc() {
   for(let i = 0; i < 10; i++) {
     let v = 5;

     // what variables are "in scope" in this line?
   }
  }  
  newFunc(); 
}

JavaScript 使用词法作用域,即:无论何时或如何调用某些代码都无关紧要,代码本身可访问的内容由代码本身的结构定义(即括号的嵌套) .

这很简单:对于某个语句,您只需查看包含该语句的每个代码块。在上面的注释行中,代码可以访问许多变量:

  • v
  • i
  • param
  • a
  • 全局范围内的所有内容

尽管代码的结构定义了每个语句可以访问的内容,但 每个变量的生命周期由使用它的代码的生命周期决定。 换句话说,一个 lifetime 的变量(我使用这个术语而不是范围, 因为我认为它更好地描述了相关部分)将被终止,比如说, 功能块的结尾,可以被其他功能“捕获”,并最终在 后者的执行范围。这就是闭包的概念,也是闭包的来源 许多有趣的技术。

所以,我想说:

  • 代码块的词法范围包括每个周围的“结构”(代码块、 函数、类或模块边界)。
  • 对于可以通过词法范围“看到”它的每一行,变量都在“范围内”。

变量生命周期从对应varletconst开始 声明被执行,并在没有代码保持它“活着”时结束,要么 直接或通过闭包。

我很确定可以添加许多额外的细节,指定旧的 var 语句及其功能范围,并将其与 letconst 块范围进行比较,但这个答案似乎已经很长了:D

【讨论】:

  • 嗨@A。基耶萨。感谢您的帮助。我写了一个short thing 试图根据您的回复来理解现在的事情。如果您想看看您是否同意,我将不胜感激。再次感谢。
  • 请注意,“全局”范围并不是说变量是全局的。在每个 JS 环境中都有一个适当的全局对象。在浏览器中,它被称为window(在节点中它是global),所以如果你写var v = 10; console.log(window.v);,你会得到10。你的例子带有一个名为main的函数完全失去了这个方面。
  • 此外,关于什么是块作用域的解释相当复杂:只需“追逐”花括号,您就会找到所有可能的块。函数词法范围包括嵌套函数定义的每个块。我想说不存在“可变范围”这样的东西:这个概念使整个机制看起来比实际更复杂。
  • 无关紧要,IMO。关于没有变量的代码块是否是作用域的讨论就像讨论一棵树在没有人能听到的情况下倒下是否会发出噪音一样。谁在乎?变量解析唯一相关的是访问变量的代码在哪里以及引用的变量在哪里定义。在我看来,其他一切都无关紧要(嗯,几乎可以肯定,它在 V8 中的即时优化器中以不同的方式实现,但在编码时并没有任何实际区别)。
  • 换句话说,当你访问变量 i 时,如果引擎因为没有定义而跳过检查最里面的块,或者检查它并发现 i 变量没有在那里定义,这完全无关紧要。谁在乎?问题是 for 循环的主体可以访问 i 变量,因为它是在包含词法范围内定义的。当 for 循环结束时,没有代码仍然引用 i,因此它会被垃圾收集。如果本机代码优化器在栈或堆上分配 i,它是否知道它的类型,这是一个实现细节。
猜你喜欢
  • 1970-01-01
  • 2012-05-26
  • 2020-06-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-26
  • 2011-02-13
  • 1970-01-01
相关资源
最近更新 更多