词汇范围的简要介绍是什么?


当前回答

在简单的语言中,词法作用域是在作用域外部定义的变量,或者更高的作用域在作用域内部自动可用,这意味着您不需要在那里传递它。

例子:

let str="JavaScript";

const myFun = () => {
    console.log(str);
}

myFun();

//输出:JavaScript

其他回答

JavaScript中的词法作用域意味着在函数外部定义的变量可以在变量声明之后定义的另一个函数内部访问。但事实并非相反;在函数内部定义的变量在该函数外部无法访问。

这个概念在JavaScript中的闭包中大量使用。

假设我们有以下代码。

var x = 2;
var add = function() {
    var y = 1;
    return x + y;
};

现在,当您调用add()-->时,这将打印3。

因此,add()函数正在访问在方法函数add之前定义的全局变量x。这是由于JavaScript中的词法作用域而调用的。

词汇作用域意味着在嵌套的函数组中,内部函数可以访问其父作用域的变量和其他资源。

这意味着孩子的函数在词汇上与父母的执行上下文绑定。

词汇范围有时也称为静态范围。

function grandfather() {
    var name = 'Hammad';
    // 'likes' is not accessible here
    function parent() {
        // 'name' is accessible here
        // 'likes' is not accessible here
        function child() {
            // Innermost level of the scope chain
            // 'name' is also accessible here
            var likes = 'Coding';
        }
    }
}

关于词法作用域,你会注意到它是向前的,这意味着可以通过其子级的执行上下文访问该名称。

但它不能向其父代反向工作,这意味着变量likes不能被其父代访问。

这还告诉我们,在不同的执行上下文中具有相同名称的变量从执行堆栈的顶部到底部获得优先级。

在最里面的函数(执行堆栈的最顶层上下文)中,名称与另一个变量类似的变量将具有更高的优先级。

来源

围绕词汇和动态作用域的对话中有一个重要的部分是缺失的:作用域变量的生存期或何时可以访问变量的简单解释。

动态范围界定与“全局”范围界定在我们传统的思考方式中只是非常松散地对应(我之所以将两者进行比较,是因为已经提到了这一点——我并不特别喜欢链接文章的解释);我们最好不要在全局变量和动态变量之间进行比较,尽管根据链接文章的说法,“……[它]作为全局范围变量的替代品很有用。”

那么,用简单的英语来说,这两种范围界定机制之间的重要区别是什么?

在上面的答案中,词汇范围定义得很好:词汇范围变量在定义它的函数的本地级别是可用的,或者是可访问的。

然而,由于它不是OP的重点,动态范围界定并没有得到太多的关注,它得到的关注意味着它可能需要更多的关注(这不是对其他答案的批评,而是“哦,那个答案让我们希望有更多的关注”)。所以,这里还有一点:

动态作用域意味着在函数调用的生命周期内,或在函数执行时,较大的程序可以访问变量。实际上,维基百科在解释两者之间的差异方面做得很好。为了避免混淆,下面是描述动态范围的文本:

…[I]n动态作用域(或动态作用域),如果变量名的作用域是则其范围是函数正在执行:当函数运行时,变量name存在,并绑定到其变量,但在函数之后返回,变量名不存在。

范围定义了函数、变量等可用的区域。例如,变量的可用性是在其上下文中定义的,比如函数、文件或对象,它们是在中定义的。我们通常称这些局部变量。

词法部分意味着你可以从阅读源代码中获得范围。

词汇范围也称为静态范围。

动态范围定义了全局变量,这些变量在定义后可以从任何地方调用或引用。有时它们被称为全局变量,尽管大多数编程语言中的全局变量都属于词汇范围。这意味着,可以通过读取代码得出变量在此上下文中可用。也许必须遵循uses或includes子句才能找到实例化或定义,但代码/编译器知道此处的变量。

相反,在动态作用域中,首先搜索本地函数,然后搜索调用本地函数的函数,然后在调用该函数的函数中搜索,依此类推,直到调用堆栈。“动态”是指更改,因为每次调用给定函数时,调用堆栈都可能不同,因此函数可能会根据从何处调用而命中不同的变量。(见此处)

要查看动态范围的有趣示例,请参阅此处。

有关详细信息,请参阅此处和此处。

Delphi/Object Pascal中的一些示例

Delphi具有词汇范围。

unit Main;
uses aUnit;  // makes available all variables in interface section of aUnit

interface

  var aGlobal: string; // global in the scope of all units that use Main;
  type 
    TmyClass = class
      strict private aPrivateVar: Integer; // only known by objects of this class type
                                    // lexical: within class definition, 
                                    // reserved word private   
      public aPublicVar: double;    // known to everyboday that has access to a 
                                    // object of this class type
    end;

implementation

  var aLocalGlobal: string; // known to all functions following 
                            // the definition in this unit    

end.

Delphi最接近动态范围的是RegisterClass()/GetClass()函数对。有关其用途,请参见此处。

假设调用RegisterClass([TmyClass])来注册某个类的时间无法通过读取代码来预测(它是在用户调用的按钮单击方法中调用的),调用GetClass('MyClass')的代码将得到结果或不得到结果。对RegisterClass()的调用不必在使用GetClass()单元的词法范围内;

动态范围的另一种可能性是Delphi2009中的匿名方法(闭包),因为它们知道调用函数的变量。它不会递归地遵循调用路径,因此不是完全动态的。

在这个问题上,我们可以从另一个角度出发,后退一步,看看范围界定在更大的解释框架(运行程序)中的作用。换句话说,假设您正在为一种语言构建一个解释器(或编译器),并负责计算输出,给定一个程序和一些输入。

解释包括跟踪三件事:

状态-即堆和堆栈上的变量和引用内存位置。对该状态的操作,即程序中的每一行代码给定操作运行的环境,即状态在操作上的投影。

解释器从程序中的第一行代码开始,计算其环境,在该环境中运行该行,并捕获其对程序状态的影响。然后,它遵循程序的控制流执行下一行代码,并重复该过程直到程序结束。

为任何操作计算环境的方式都是通过编程语言定义的一组正式规则。术语“绑定”经常用于描述程序的整体状态到环境中的值的映射。注意,我们所说的“总体状态”不是指全局状态,而是指在执行过程中的任何一点上每个可到达定义的总和)。

这是定义范围问题的框架。现在进入下一部分我们的选择。

作为解释器的实现者,您可以通过使环境尽可能接近程序的状态来简化任务。因此,无论前一行是赋值、函数调用、从函数返回还是控制结构(如while循环),一行代码的环境都将简单地由前一行代码环境定义,并将该操作的效果应用于该行代码。

这就是动态作用域的要点,其中任何代码运行的环境都绑定到由其执行上下文定义的程序状态。

或者,你可以想象一个程序员使用你的语言,简化他或她的跟踪变量取值的任务。关于过去执行的全部结果的推理涉及太多的路径和太多的复杂性。词法作用域通过将当前环境限制在当前块、函数或其他作用域单元及其父级(即包围当前时钟的块或调用当前函数的函数)中定义的状态部分来帮助实现这一点。

换句话说,使用词法作用域,任何代码所看到的环境都绑定到与语言中明确定义的作用域(如块或函数)相关联的状态。