什么是词汇范围?

问题描述 投票:594回答:15

有人可以给我一个关于词汇范围的简要介绍吗?

javascript scoping lexical-scope
15个回答
631
投票

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

首先,词典范围(也称为静态范围),采用类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可以访问xdummy1中的dummy2,或任何x中使用fun中声明的x函数。

dummy1();

将打印5,

dummy2();

将打印10。

第一个称为静态,因为它可以在编译时推断,第二个称为动态,因为外部作用域是动态的,取决于函数的链调用。

我发现静态范围对于眼睛更容易。大多数语言最终都是这样,甚至Lisp(可以做到这两种,对吗?)。动态范围就像将所有变量的引用传递给被调用函数。

编译器无法推断出函数的外部动态范围的一个例子,考虑我们的最后一个例子,如果我们写这样的东西:

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

调用链取决于运行时条件。如果是,则调用链如下所示:

dummy1 --> fun()

如果条件为假:

dummy2 --> fun()

在两种情况下,fun的外部范围是调用者加上调用者的调用者,依此类推。

只是提到C语言不允许嵌套函数或动态作用域。


3
投票

词法范围意味着函数在定义它的上下文中查找变量,而不是在其周围的范围内查找变量。

如果您想了解更多细节,请查看Lisp中词汇范围的工作原理。 Kyle Cronin在Dynamic and Lexical variables in Common Lisp中选择的答案比这里的答案要清楚得多。

巧合的是,我只在Lisp类中学到了这一点,它恰好也适用于JS。

我在chrome的控制台中运行了这段代码。

// javascript               equivalent Lisp
var x = 5;                //(setf x 5)
console.debug(x);         //(print x)
function print_x(){       //(defun print-x () 
    console.debug(x);     //    (print x)
}                         //)
(function(){              //(let  
    var x = 10;           //    ((x 10))
    console.debug(x);     //    (print x)
    print_x();            //    (print-x)
})();                     //)

输出:

5
10
5 

2
投票

Javascript中的词法范围意味着在函数外部定义的变量可以在变量声明之后定义的另一个函数内访问。但事实恰恰相反,函数内部定义的变量在该函数之外是不可访问的。

这个概念在Javascript中用于闭包。

假设我们有以下代码。

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

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

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


0
投票

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

口译涉及跟踪三件事:

1)状态 - 即堆和堆栈上的变量和引用的内存位置。

2)对该状态的操作 - 即程序中的每行代码

3)特定行动运行的环境 - 即国家对行动的预测。

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

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

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

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

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

  • 或者,您可以想到程序员使用您的语言并简化他或她跟踪变量可以采用的值的任务。在推理过去执行的总体结果时,有太多的路径和太多的复杂性。词法作用域通过将当前环境限制为当前块,函数或其他作用域单元及其父级(即包含当前时钟的块或调用当前函数的函数)中定义的状态部分来帮助实现此目的。

换句话说,对于词法范围,任何代码看到的环境都绑定到与语言中明确定义的范围相关联的状态,例如块或函数。


0
投票

词法范围意味着在嵌套的函数组中,内部函数可以访问其父作用域的变量和其他资源。这意味着子函数在词法上绑定到其父项的执行上下文。词法范围有时也称为静态范围。

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';
        }
    }
}

你会注意到关于词法范围的事情是它向前工作,这意味着名称可以通过其子项的执行上下文来访问。但它不会向其父母反向工作,这意味着它的父母无法访问变量。这也告诉我们,在不同的执行上下文中具有相同名称的变量从执行堆栈的顶部到底部优先。在最里面的函数(执行堆栈的最顶层上下文)中,具有与另一个变量类似的名称的变量将具有更高的优先级。

请注意,这取自here


0
投票

我通常通过示例来学习,这里有一些东西:

const lives = 0;

function catCircus () {
    this.lives = 1;
    const lives = 2;

    const cat1 = {
        lives: 5,
        jumps: () => {
            console.log(this.lives);
        }
    };
    cat1.jumps(); // 1
    console.log(cat1); // { lives: 5, jumps: [Function: jumps] }

    const cat2 = {
        lives: 5,
        jumps: () => {
            console.log(lives);
        }
    };
    cat2.jumps(); // 2
    console.log(cat2); // { lives: 5, jumps: [Function: jumps] }

    const cat3 = {
        lives: 5,
        jumps: () => {
            const lives = 3;
            console.log(lives);
        }
    };
    cat3.jumps(); // 3
    console.log(cat3); // { lives: 5, jumps: [Function: jumps] }

    const cat4 = {
        lives: 5,
        jumps: function () {
            console.log(lives);
        }
    };
    cat4.jumps(); // 2
    console.log(cat4); // { lives: 5, jumps: [Function: jumps] }

    const cat5 = {
        lives: 5,
        jumps: function () {
            var lives = 4;
            console.log(lives);
        }
    };
    cat5.jumps(); // 4
    console.log(cat5); // { lives: 5, jumps: [Function: jumps] }

    const cat6 = {
        lives: 5,
        jumps: function () {
            console.log(this.lives);
        }
    };
    cat6.jumps(); // 5
    console.log(cat6); // { lives: 5, jumps: [Function: jumps] }

    const cat7 = {
        lives: 5,
        jumps: function thrownOutOfWindow () {
            console.log(this.lives);
        }
    };
    cat7.jumps(); // 5
    console.log(cat7); // { lives: 5, jumps: [Function: thrownOutOfWindow] }
}

catCircus();

0
投票

在简单语言中,词法范围是在范围之外定义的变量,或者在范围内自动提供上限范围,这意味着您不需要将其传递到那里。

例如:

let str="JavaScript";

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

myFun();

//输出:JavaScript


242
投票

让我们尝试最短的定义:

词法范围定义如何在嵌套函数中解析变量名称:内部函数包含父函数的范围,即使父函数已返回。

这就是它的全部!


46
投票
var scope = "I am global";
function whatismyscope(){
   var scope = "I am just a local";
   function func() {return scope;}
   return func;
}

whatismyscope()()

上面的代码将返回“我只是一个本地”。它不会回归“我是一个全球化的”。因为函数func()计算最初定义的位于函数whatismyscope范围内的位置。

它不会受到任何调用(全局范围/来自另一个函数)的困扰,这就是为什么全局范围值我是全局的不会被打印的原因。

这称为词法作用域,其中“函数使用在定义时生效的作用域链执行” - 根据JavaScript定义指南。

词汇范围是一个非常强大的概念。

希望这可以帮助..:)


38
投票

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

词汇部分意味着您可以从阅读源代码中派生范围。

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

动态范围定义了可在定义后从任何位置调用或引用的全局变量。有时它们被称为全局变量,即使大多数programmin语言中的全局变量都是词法范围。这意味着,它可以从读取变量在此上下文中可用的代码中派生而来。也许必须遵循uses或includes子句来查找instatiation或定义,但代码/编译器知道这个地方的变量。

相比之下,在动态范围中,首先在本地函数中搜索,然后在调用本地函数的函数中搜索,然后在调用该函数的函数中搜索,依此类推,调用堆栈。 “动态”指的是变化,因为每次调用给定函数时调用堆栈都可以不同,因此函数可能会根据调用它的位置命中不同的变量。 (见here

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

有关详细信息,请参阅herehere

Delphi / Object Pascal中的一些示例

德尔福有词汇范围。

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()函数对。有关其用途,请参阅here

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

动态范围的另一种可能性是Delphi 2009中的anonymous methods(闭包),因为他们知道它们的调用函数的变量。它不会递归地跟随调用路径,因此不是完全动态的。


36
投票

词法(AKA静态)范围是指仅根据其在文本语料库中的位置来确定变量的范围。变量始终指向其顶级环境。在relation to dynamic scope.中了解它很好


31
投票

我喜欢@Arak等人的全功能,语言无关的答案。既然这个问题被标记为JavaScript,我想填写一些非常特定于这种语言的注释。

在javascript中,我们对范围界定的选择是:

  • 原样(没有范围调整)
  • 词汇var _this = this; function callback(){ console.log(_this); }
  • 绑定callback.bind(this)

值得注意的是,我认为,JavaScript doesn't really have dynamic scoping.bind调整了this关键字,这很接近,但技术上并不相同。

以下是展示这两种方法的示例。每次做出关于如何调整回调范围的决定时都会这样做,因此这适用于promises,事件处理程序等。

Lexical

以下是您在JavaScript中可能称之为Lexical Scoping的回调:

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`
  }
//...

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


12
投票

词法作用域:在函数外部声明的变量是全局变量,在JavaScript程序中随处可见。在函数内声明的变量具有函数作用域,并且仅对出现在该函数内的代码可见。


12
投票

IBM将其定义为:

申请适用的程序或分部单位的部分。在例程中以及所有嵌套例程中已知例程中声明的标识符。如果嵌套例程声明具有相同名称的项,则外部项在嵌套例程中不可用。

例1:

function x() {
    /*
    Variable 'a' is only available to function 'x' and function 'y'.
    In other words the area defined by 'x' is the lexical scope of
    variable 'a'
    */
    var a = "I am a";

    function y() {
        console.log( a )
    }
    y();

}
// outputs 'I am a'
x();

例2:

function x() {

    var a = "I am a";

    function y() {
         /*
         If a nested routine declares an item with the same name,
         the outer item is not available in the nested routine.
         */
        var a = 'I am inner a';
        console.log( a )
    }
    y();

}
// outputs 'I am inner a'
x();

4
投票

围绕词法和动态范围的对话中有一个重要部分缺失:对范围变量的生命周期的简单解释 - 或者可以访问变量。

动态范围只是非常宽松地对应于我们传统上考虑它的方式的“全局”范围(我提出两者之间的比较的原因是它已经是mentioned - 我不特别喜欢linked文章的解释);我们最好不要在全局和动态之间进行比较 - 尽管根据链接的文章,“... [它]可以替代全局范围的变量。”

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

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

然而 - 因为它不是OP的焦点 - 动态范围没有得到很多关注,它所获得的关注意味着它可能需要更多(这不是对其他答案的批评,而是“哦,这个答案让我们希望有更多的“)。所以,这里有一点点:

动态范围意味着在函数调用的生命周期中,较大的程序可以访问变量 - 或者在函数执行时。实际上,维基百科实际上在两者之间使用explanation of the difference做得很好。为了不混淆它,这里是描述动态范围的文本:

... [I] n动态范围(或动态范围),如果变量名称的范围是某个函数,则其范围是函数执行的时间段:当函数运行时,变量名称存在,并绑定到其变量,但在函数返回后,变量名称不存在。

© www.soinside.com 2019 - 2024. All rights reserved.