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


当前回答

词法范围:在函数外部声明的变量是全局变量,在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不能被其父代访问。

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

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

来源

我通过例子来理解它们。:)

首先,词法作用域(也称为静态作用域),在类似C的语法中:

void fun()
{
    int x = 5;

    void fun2()
    {
        printf("%d", x);
    }
}

每个内部级别都可以访问其外部级别。

还有另一种方式,称为Lisp的第一个实现使用的动态范围,同样是类似C的语法:

void fun()
{
    printf("%d", x);
}

void dummy1()
{
    int x = 5;

    fun();
}

void dummy2()
{
    int x = 10;

    fun();
}

在这里,fun可以访问dummy1或dummy2中的x,也可以访问任何函数中声明了x的调用fun的任何x。

dummy1();

将打印5,

dummy2();

将打印10。

第一个被称为静态的,因为它可以在编译时推导出来;第二个被称之为动态的,因为外部范围是动态的,并且取决于函数的链调用。

我发现静态观察对眼睛来说更容易。大多数语言最终都走上了这条路,甚至Lisp(两者都能做到,对吧?)。动态作用域类似于将所有变量的引用传递给被调用函数。

作为编译器无法推断函数的外部动态范围的示例,请考虑我们的最后一个示例。如果我们这样写:

if(/* some condition */)
    dummy1();
else
    dummy2();

调用链取决于运行时条件。如果为真,则调用链看起来像:

dummy1 --> fun()

如果条件为假:

dummy2 --> fun()

在这两种情况下,乐趣的外部范围都是调用者加上调用者的调用者等等。

只需一提,C语言既不允许嵌套函数,也不允许动态作用域。

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

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

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

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

然而,由于它不是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中的匿名方法(闭包),因为它们知道调用函数的变量。它不会递归地遵循调用路径,因此不是完全动态的。