什么是词汇范围?

Sub*_*Rao 645 javascript scoping lexical-scope

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

Ara*_*raK 653

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

首先,词典范围(也称为静态范围),采用类C语法:

void fun()
{
    int x = 5;

    void fun2()
    {
        printf("%d", x);
    }
}
Run Code Online (Sandbox Code Playgroud)

每个内在级别都可以访问其外层.

还有另一种方法,称为动态范围,首先实现Lisp,再次使用类似C语法的语法:

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

void dummy1()
{
    int x = 5;

    fun();
}

void dummy2()
{
    int x = 10;

    fun();
}
Run Code Online (Sandbox Code Playgroud)

在这里fun既可以访问xdummy1dummy2,或x在调用任何函数funx在其声明.

dummy1();
Run Code Online (Sandbox Code Playgroud)

将打印5,

dummy2();
Run Code Online (Sandbox Code Playgroud)

将打印10.

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

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

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

if(/* some condition */)
    dummy1();
else
    dummy2();
Run Code Online (Sandbox Code Playgroud)

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

dummy1 --> fun()
Run Code Online (Sandbox Code Playgroud)

如果条件为假:

dummy2 --> fun()
Run Code Online (Sandbox Code Playgroud)

fun两种情况的外部范围都是调用者加上调用者的调用者等等.

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

  • 我还想指出一个我刚刚发现的非常容易理解的教程.Arak的例子很好,但对于需要更多示例的人来说可能太短了(实际上,与其他语言相比......).看一看.理解*this*很重要,因为该关键字将引导我们理解词汇范围.http://howtonode.org/what-is-this (15认同)
  • 这是一个很好的答案.但问题是用"JavaScript"标记.因此,我认为这不应该被标记为已接受的答案.JS中的词汇范围是不同的 (6认同)
  • 非常好的答案.谢谢.@Boyang我不同意.我不是Lisp编码器,但发现Lisp示例很有用,因为它是动态范围的一个例子,你在JS中没有. (4认同)
  • 最初我认为这个例子是有效的C代码,并且对C中是否存在动态范围感到困惑.也许最后的免责声明可能会转移到代码示例之前? (2认同)
  • 这仍然是一个非常有用的答案,但我认为@Boyang 是正确的。这个答案指的是“级别”,它更符合 C 的块范围。JavaScript 默认没有块级作用域,所以在 `for` 循环中是典型的问题。JavaScript 的词法范围仅在函数级别,除非使用 ES6 `let` 或 `const`。 (2认同)

Pie*_*ing 254

让我们尝试最短的定义:

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

这就是它的全部!

  • 最后一部分:“即使父函数已返回”也被称为Closure。 (11认同)
  • 仅用一句话就理解了词汇范围和结束语。谢谢!! (8认同)

kta*_*kta 51

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

whatismyscope()()
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

希望这可以帮助..:)

  • 这是非常好的解释,我想再添加一件事,如果你编写 function func() {return this.scope;} 那么它将返回“I am global”只需使用此关键字,你的范围就会改变 (5认同)

Ral*_*ach 38

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

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

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

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

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

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

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

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.
Run Code Online (Sandbox Code Playgroud)

最接近Delphi的动态范围是RegisterClass()/ GetClass()函数对.有用,请看这里.

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

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

  • 实际上私人可以在定义类的整个单元中访问.这就是D2006中引入"严格私密"的原因. (2认同)
  • 普通语言的+1(与复杂的语言和没有太多描述的例子相对) (2认同)

Eva*_*her 37

词法(AKA静态)范围是指仅根据其在文本语料库中的位置来确定变量的范围.变量始终指向其顶级环境.与动态范围相关的理解很好.


Sim*_*lGy 33

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

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

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

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

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

词汇

以下是Lexical ScopingJavaScript中可能的回调术语:

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
  }
  //...
};
Run Code Online (Sandbox Code Playgroud)

范围的另一种方法是使用Function.prototype.bind:

var downloadManager = {
  initialize: function() {
    $('.downloadLink').on('click', function () {
      this.startDownload();
    }.bind(this)); // create a function object bound to `this`
  }
//...
Run Code Online (Sandbox Code Playgroud)

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

  • 使用“bind”不会影响范围。 (2认同)

Shi*_*pta 21

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

这意味着子函数在词法上绑定到其父函数的执行上下文。

词法作用域有时也称为静态作用域

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';
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

但它不会向后返回到其父项,这意味着其父项likes无法访问该变量。

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

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

来源


Pra*_*hor 20

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

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

假设我们有以下代码。

var x = 2;
var add = function() {
    var y = 1;
    return x + y;
};
Run Code Online (Sandbox Code Playgroud)

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

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

  • 考虑该代码片段适用于动态范围的语言。如果在给定的代码片段之后立即调用“add()”函数,它也会打印 3。词法作用域不仅仅意味着函数可以访问本地上下文之外的全局变量。因此,示例代码实际上无助于展示词法作用域的含义。在代码中显示词法作用域确实需要一个反例,或者至少需要解释代码的其他可能解释。 (3认同)

J.K*_*.A. 17

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

例子:

let str="JavaScript";

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

myFun();
Run Code Online (Sandbox Code Playgroud)

// 输出:JavaScript

  • 对我来说最短和最好的答案是一个例子。可以添加 ES6 的箭头函数解决了“bind”的问题。有了它们,不再需要“绑定”。有关此更改的更多信息,请检查 /sf/answers/2405296631/ (2认同)

小智 12

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


Rob*_*cha 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();
Run Code Online (Sandbox Code Playgroud)

例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();
Run Code Online (Sandbox Code Playgroud)


rat*_*ile 8

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

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

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

我在 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)
})();                     //)
Run Code Online (Sandbox Code Playgroud)

输出:

5
10
5
Run Code Online (Sandbox Code Playgroud)

  • 这是最有帮助的答案。如果您同意,请点赞。 (2认同)

Sea*_*ean 7

我希望这对您有所帮助,这是我尝试给出的稍微更抽象的定义:

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

Fwiw,我的逻辑只是基于以下定义构建的:

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

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

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

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


Ken*_*ard 6

词法范围是指从执行堆栈中的当前位置可见的标识符(例如变量、函数等)的词典。

\n\n
- global execution context\n    - foo\n    - bar\n    - function1 execution context\n        - foo2\n        - bar2\n        - function2 execution context\n            - foo3\n            - bar3\n
Run Code Online (Sandbox Code Playgroud)\n\n

foo并且bar始终位于可用标识符的词典中,因为它们是全局的。

\n\n

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

\n\n

执行时function2,它可以访问词典foo3, bar3, foo2, bar2, foo, 和bar

\n\n

全局和/或外部函数无法访问内部函数标识符的原因是因为该函数的执行尚未发生,因此其标识符尚未分配给内存。更重要的是,一旦内部上下文执行,它就会从执行堆栈中删除,这意味着所有 xe2\x80\x99s 标识符都已被垃圾收集并且不再可用。

\n\n

最后,这就是为什么嵌套执行上下文始终可以访问它\xe2\x80\x99 的祖先执行上下文,从而可以访问更大的标识符词典。

\n\n

看:

\n\n\n\n

特别感谢@robr3rd帮助简化上述定义。

\n