什么是词汇范围?

时间:2009-06-26 05:10:16

标签: javascript scoping lexical-scope

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

18 个答案:

答案 0 :(得分:624)

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

首先,词汇范围(也称为静态范围),采用类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 1}}在其中声明。

x

将打印5,

dummy1();

将打印10。

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

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

编译器为什么不能推导出函数的外部动态范围的一个例子,考虑我们的最后一个例子,如果我们写这样的东西:

dummy2();

调用链取决于运行时条件。如果是,那么调用链看起来像:

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

如果条件为假:

dummy1 --> fun()

两种情况下dummy2 --> fun() 的外部范围是调用者加上调用者的调用者,依此类推

提到C语言不允许嵌套函数或动态范围。

答案 1 :(得分:237)

让我们尝试最短的定义:

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

这就是它的全部!

答案 2 :(得分:45)

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

whatismyscope()()

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

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

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

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

希望这会有所帮助.. :)

答案 3 :(得分: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(闭包),因为他们知道调用函数的变量。它不会递归地跟随调用路径,因此不是完全动态的。

答案 4 :(得分:36)

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

中理解它很好

答案 5 :(得分:31)

我喜欢@Arak等人的全功能,语言无关的答案。由于这个问题被标记为 JavaScript ,我想填写一些非常具体的语言。

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

  • as-is(无范围调整)
  • lexical 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`
  }
//...

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

答案 6 :(得分: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();

答案 7 :(得分:11)

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

答案 8 :(得分:4)

关于词法和动态范围的对话的一个重要部分是缺失的:对范围变量的生命周期的简单解释 - 或者当变量时被访问。

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

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

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

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

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

  

... [I] n动态范围(或动态范围),如果变量名称的范围是a   某些功能,那么它的范围就是其中的时间段   函数正在执行:当函数运行时,变量   name存在,并且绑定到它的变量,但是在函数之后   返回时,变量名称不存在。

答案 9 :(得分: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 

答案 10 :(得分:3)

Javascript中的词法作用域意味着在函数外部定义的变量可以在变量声明后定义的另一个函数内部访问。但是事实并非如此,在函数内部定义的变量将无法在该函数外部访问。

这个概念在Java语言的闭包中大量使用。

让我们说下面的代码。

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

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

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

答案 11 :(得分:1)

词法作用域是指从执行堆栈中的当前位置可见的标识符的词法(例如变量,函数等)。

- global execution context
    - foo
    - bar
    - function1 execution context
        - foo2
        - bar2
        - function2 execution context
            - foo3
            - bar3

foobar始终在可用标识符的词典中,因为它们是全局的。

执行function1时,它可以访问foo2bar2foobar的词典。

执行function2时,它可以访问foo3bar3foo2bar2foobar

全局和/或外部函数无法访问内部函数标识符的原因是因为尚未执行该函数,因此,没有将其标识符分配给内存。而且,一旦内部上下文执行完毕,便会从执行堆栈中删除它,这意味着其所有标识符均已被垃圾回收,并且不再可用。

最后,这就是为什么嵌套执行上下文始终可以访问其祖先执行上下文的原因,因此也可以访问更大的标识符词典。

请参阅:

特别感谢@robr3rd为简化上述定义提供的帮助。

答案 12 :(得分:1)

一个古老的问题,但这是我的看法。

词法(静态)范围是指源代码中变量的范围。

在像JavaScript这样的语言中,函数可以被传递,附加和重新附加到其他对象,尽管您的范围可能取决于当时谁在调用该函数,但事实并非如此。以这种方式更改范围将是动态范围,而JavaScript则不会这样做,除非使用this对象引用。

说明要点:

var a='apple';

function doit() {
    var a='aardvark';
    return function() {
        alert(a);
    }
}

var test=doit();
test();

在此示例中,变量a是全局定义的,但在doit()函数中却没有显示。如您所见,该函数返回另一个函数,该函数依赖于其自身作用域之外的a变量。

如果运行此命令,则会发现使用的值是aardvark,而不是apple,尽管它在test()函数的范围内,但不在词法中原始功能的范围。也就是说,使用的范围是源代码中显示的范围,而不是实际使用该功能的范围。

这个事实会产生令人讨厌的后果。例如,您可能决定单独组织函数,然后在需要的时候使用它们,例如在事件处理程序中使用,会更容易:

var a='apple',b='banana';

function init() {
  var a='aardvark',b='bandicoot';
  document.querySelector('button#a').onclick=function(event) {
    alert(a);
  }
  document.querySelector('button#b').onclick=doB;
}

function doB(event) {
  alert(b);
}

init();
<button id="a">A</button>
<button id="b">B</button>

此代码示例执行每个操作。您可以看到,由于词法作用域,按钮A使用了内部变量,而按钮B没有使用内部变量。嵌套函数的最终结果可能超出您的期望。

顺便说一下,在两个示例中,您还将注意到即使包含函数功能已经运行,内部词法作用域变量仍然存在。这称为 closure ,它是指嵌套函数对外部变量的访问,即使外部函数已完成也是如此。 JavaScript必须足够聪明才能确定是否不再需要这些变量,如果不需要,可以对其进行垃圾收集。

答案 13 :(得分:0)

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

口译涉及跟踪三件事:

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

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

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

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

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

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

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

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

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

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

答案 14 :(得分: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';
        }
    }
}
  

您会发现有关词法范围的事情是它可以工作   向前,意味着名称可以通过其子级的执行来访问   上下文。但这对父母没有反作用,这意味着   变量likes不能被其父母访问。这也说明   我们在不同的执行上下文中具有相同名称的变量   从执行堆栈的顶部到底部获得优先级。一个变量,   最内层函数中具有与另一个变量相似的名称   (执行堆栈的最高上下文)将具有更高的优先级。

请注意,这取自here

答案 15 :(得分: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();

答案 16 :(得分:0)

用简单的语言来说,词法作用域是在作用域之外定义的变量,或者上限作用域在作用域内部是自动可用的,这意味着您无需在其中传递它。

例如:

let str="JavaScript";

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

myFun();

//输出:JavaScript

答案 17 :(得分:0)

我希望这是有帮助的,这是我尝试更抽象的定义:

词法范围: 某些东西(例如函数或变量)对程序中其他元素的访问或范围,由其在源代码中的位置决定。

Fwiw,我这里的逻辑只是建立在以下定义中:

词法:与语言的单词或词汇有关(特别是与语法或结构分开的单词){在我们的例子中 - 一种编程语言}。

Scope(名词):操作范围{在我们的例子中范围是:可以访问什么}。

请注意,ALGOL 60 规范中 Lexical Scope 的原始 1960 definition 比我上面的尝试简洁得多:

词法范围:应用名称与实体绑定的源代码部分。 source