什么是词汇范围?

2020/09/22 00:41 · javascript ·  · 0评论

什么是词汇作用域简介?

我通过示例了解它们。:)

首先,采用类似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既可以访问xdummy1dummy2,或x在调用任何函数funx在其声明。

dummy1();

将打印5

dummy2();

将打印10。

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

我发现静态范围界定对眼睛来说更容易。最终,大多数语言都采用了这种方式,甚至Lisp也是如此(对吗?)。动态作用域就像将所有变量的引用传递给调用的函数一样。

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

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

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

dummy1 --> fun()

如果条件为假:

dummy2 --> fun()

fun两种情况的外部范围都是调用方加上调用方的调用方,依此类推

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

让我们尝试最短的定义:

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

这就是全部!

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

whatismyscope()()

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

它不会被调用的内容所困扰(即使是全局作用域,甚至也不会从另一个函数中),这就是为什么不会打印我是全局的全局作用域值的原因。

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

词法作用域是一个非常非常强大的概念。

希望这可以帮助..:)

词法(AKA静态)作用域是指仅根据变量在代码文本语料库中的位置来确定变量的范围。变量始终引用其顶层环境。最好将其与动态范围相关联。

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

词法部分意味着您可以通过阅读源代码来得出范围。

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

动态范围定义了可以在定义后从任何地方调用或引用的全局变量。有时它们被称为全局变量,即使大多数programmin语言中的全局变量具有词法范围。这意味着,可以从读取代码中得出该变量在此上下文中可用的信息。也许必须遵循use或include子句才能找到实例或定义,但是代码/编译器知道该位置的变量。

相比之下,在动态作用域中,您首先搜索本地函数,然后搜索调用本地函数的函数,然后搜索调用该函数的函数,依此类推,直到调用堆栈。“动态”是指更改,因为每次调用给定函数时,调用堆栈都可能不同,因此该函数可能会根据调用源的不同而命中不同的变量。(请参阅此处

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

有关更多详细信息,请参见此处此处

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('TmyClass')的代码将获得结果与否。使用GetClass()调用RegisterClass()不必在单元的词汇范围内;

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

我喜欢@Arak之类的功能全面,与语言无关的答案。由于此问题被标记为JavaScript,因此,我想在某些笔记中特别介绍这种语言。

在JavaScript中,作用域的选择是:

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

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

这是展示两种方法的示例。每次决定如何确定回调范围时,您都需要执行此操作,因此这适用于Promise,事件处理程序等。

词法

这是您可能Lexical Scoping在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`
  }
//...

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

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

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();

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

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

这也告诉我们,在不同执行上下文中具有相同名称的变量从执行堆栈的顶部到底部获得优先级。最里面的函数(执行堆栈的最上层上下文)中具有与另一个变量相似名称的变量将具有更高的优先级。

请注意,这是从此处获取的

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

例:

let str="JavaScript";

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

myFun();

//输出:JavaScript

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

如果需要更多细节,请查看词汇范围在Lisp中的工作方式Kyle Cronin在Common Lisp中的Dynamic和Lexical变量中选择的答案比这里的答案要清晰得多。

碰巧的是,我只是在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)
})();                     //)

输出:

5
10
5

关于词汇动态作用域的对话中有一个重要的部分丢失了:对范围变量生存期(或何时可以访问该变量)进行简单说明

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

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

在上面的所有答案中,词法作用域的定义非常好:词法范围的变量在定义它的函数的本地级别可用(或可以访问)。

但是,由于它不是OP的重点,因此动态作用域还没有引起足够的关注,而它所获得的关注意味着它可能需要更多的关注(这不是对其他答案的批评,而是“哦,这个答案使我们希望还有更多”)。所以,这里还有更多:

动态作用域意味着在函数调用的生命周期内或函数执行期间,较大的程序可以访问变量。确实,维基百科在解释两者之间的差异方面做得很好为了避免混淆,以下是描述动态作用域的文本:

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

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

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

foo并且bar始终在可用标识符的词典之内,因为它们是全局的。

function1被执行时,它可以访问的词典foo2bar2foo,和bar

function2被执行时,它可以访问的词典foo3bar3foo2bar2foo,和bar

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

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

看到:

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

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

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

假设我们有以下代码。

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

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

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

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

解释涉及跟踪三件事:

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

  2. 在该状态下的操作-即程序中的每一行代码

  3. 给定操作运行环境 -即状态在操作上的投影

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

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

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

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

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

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

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

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

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

在像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>

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

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

我通常通过示例学习,这里有一些内容:

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();

本主题与内置bind功能密切相关,并在ECMAScript 6 Arrow Functions中引入这确实很烦人,因为对于我们要使用的每个新“类”(实际上是函数)方法,我们都必须bind这样做才能访问范围。

JavaScript默认情况下不会将其作用域设置为thison(它不会将上下文设置为on this)。默认情况下,您必须明确地说出您想要的上下文

箭头功能自动获取所谓的词法作用域(访问变量的定义在它的包含块)。使用箭头功能时,它将自动绑定this到最初定义箭头功能的位置,并且箭头功能上下文为其包含的块。

在下面最简单的示例中查看其在实践中的工作方式。

在箭头函数之前(默认情况下没有词法范围):

const programming = {
  language: "JavaScript",
  getLanguage: function() {
    return this.language;
  }
}

const globalScope = programming.getLanguage;
console.log(globalScope()); // Output: undefined

const localScope = programming.getLanguage.bind(programming);
console.log(localScope()); // Output: "JavaScript"

带箭头功能(默认情况下为词法作用域):

const programming = {
  language: "JavaScript",
  getLanguage: function() {
    return this.language;
  }
}

const arrowFunction = () => {
    console.log(programming.getLanguage());
}

arrowFunction(); // Output: "JavaScript"
本文地址:http://javascript.askforanswer.com/shenmeshicihuifanwei.html
文章标签: ,   ,  
版权声明:本文为原创文章,版权归 javascript 所有,欢迎分享本文,转载请保留出处!

文件下载

老薛主机终身7折优惠码boke112

上一篇:
下一篇:

评论已关闭!