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


当前回答

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

首先,词法作用域(也称为静态作用域),在类似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语言既不允许嵌套函数,也不允许动态作用域。

其他回答

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

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

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

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不能被其父代访问。

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

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

来源

词法作用域意味着函数从定义它们的作用域解析自由变量,而不是从它们被调用的作用域。

让我们尝试最短的定义:

词法作用域定义了如何在嵌套函数中解析变量名:即使父函数已返回,内部函数也包含父函数的作用域。

这就是它的全部!

我喜欢@Arak这样的人提供的功能齐全、语言不可知的答案。由于这个问题被标记为JavaScript,所以我想插入一些与该语言非常相关的注释。

在JavaScript中,我们的作用域选择如下:

按原样(无范围调整)词法var_this=this;函数callback(){console.log(_this);}绑定回调.bind(this)

我认为值得注意的是,JavaScript并没有真正的动态范围。bind调整this关键字,这很接近,但在技术上并不相同。

下面是一个示例,演示了这两种方法。每次决定如何确定回调的范围时,都要这样做,因此这适用于承诺、事件处理程序等。

词汇

以下是JavaScript中回调的词法范围:

var downloadManager = {
  initialize: function() {
    var _this = this; // Set up `_this` for lexical access
    $('.downloadLink').on('click', function () {
      _this.startDownload();
    });
  },
  startDownload: function(){
    this.thinking = true;
    // Request the file from the server and bind more callbacks for when it returns success or failure
  }
  //...
};

跳跃

作用域的另一种方法是使用Function.prototype.bind:

var downloadManager = {
  initialize: function() {
    $('.downloadLink').on('click', function () {
      this.startDownload();
    }.bind(this)); // Create a function object bound to `this`
  }
//...

据我所知,这些方法在行为上是等效的。

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

解释包括跟踪三件事:

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

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

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

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

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

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

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

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