什么是词汇范围?

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

280279 次浏览

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

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

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

词法作用域也称为静态作用域。

动态作用域定义了定义后可以从任何地方调用或引用的全局变量。有时它们被称为全局变量,即使大多数编程语言中的全局变量都是词法作用域。这意味着,可以从阅读代码中得出该变量在此上下文中可用。也许必须遵循使用或包括子句来找到实例化或定义,但代码/编译器知道这个地方的变量。

相比之下,在动态作用域中,你首先在局部函数中搜索,然后在调用局部函数的函数中搜索,然后在调用该函数的函数中搜索,依此类推,向上调用堆栈。“动态”指的是变化,因为每次调用给定函数时调用堆栈都可能不同,因此函数可能会根据调用它的位置命中不同的变量。(参见这里

要查看动态作用域的有趣示例,请参阅这里

有关详细信息,请参阅这里这里

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;typeTmyClass = classstrict private aPrivateVar: Integer; // only known by objects of this class type// lexical: within class definition,// reserved word privatepublic aPublicVar: double;    // known to everyboday that has access to a// object of this class typeend;
implementation
var aLocalGlobal: string; // known to all functions following// the definition in this unit
end.

Delphi最接近动态作用域的是注册类()/GetClass()函数对。关于它的使用,请参阅这里

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

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

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

首先,词汇范围(也称为静态作用域),采用类似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可以访问dummy1dummy2中的x,或者任何调用fun并在其中声明x的函数中的任何x

dummy1();

将打印5,

dummy2();

将打印10。

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

我发现静态作用域对眼睛来说更容易。大多数语言最终都走上了这条路,甚至Lisp(两者都可以,对吧?)。动态作用域就像将所有变量的引用传递给被调用的函数。

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

if(/* some condition */)dummy1();elsedummy2();

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

dummy1 --> fun()

如果条件为false:

dummy2 --> fun()

在这两种情况下,fun的外部作用域都是调用者加上来电者的来电者等等

值得一提的是,C语言不允许嵌套函数或动态范围。

让我们尝试最短的定义:

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

这就是它的全部!

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

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

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

它不会受到任何调用的干扰(全局范围/甚至来自另一个函数),这就是为什么全局范围值I am global不会被打印。

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

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

希望有帮助:)

我喜欢像@Arak这样的人提供的功能齐全、与语言无关的答案。不过,由于这个问题被标记为javascript,我想加入一些针对这种语言的注释。

在JavaScript中,我们选择的范围是:

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

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

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

词汇表

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

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

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

如果你想了解更多细节,请查看Lisp中词汇作用域的工作原理。Kyle Cronin在Common Lisp中的动态和词法变量中选择的答案比这里的答案清楚得多。

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

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

// JavaScript               Equivalent Lispvar x = 5;                //(setf x 5)console.debug(x);         //(print x)function print_x(){       //(defun print-x ()console.debug(x);     //    (print x)}                         //)(function(){              //(letvar x = 10;           //    ((x 10))console.debug(x);     //    (print x)print_x();            //    (print-x)})();                     //)

输出:

5105

围绕词汇动态范围界定的对话中缺少一个重要部分:对作用域变量一生的简单解释-或者可以访问变量

动态作用域只是非常松散地对应于我们传统上认为的“全局”作用域(我提出两者之间的比较的原因是它已经是链接047479">提到了-我不太喜欢链接文章的解释);最好不要在全局和动态之间进行比较-尽管据说,根据链接的文章,“…[它]作为全局作用域变量的替代品很有用。

那么,简单地说,两种范围界定机制之间的重要区别是什么?

词法作用域在上面的答案中定义得非常好:词法作用域变量在定义它的函数的局部级别可用——或者可以访问。

然而,由于它不是OP的重点,动态范围界定没有得到太多的关注,而它得到的关注意味着它可能需要更多的关注(这不是对其他答案的批评,而是“哦,那个答案让我们希望有更多”)。

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

…[I]n动态作用域(或动态作用域),如果变量名的作用域是某个函数,那么它的范围就是函数正在执行:函数运行时,变量name存在,并绑定到其变量,但在函数之后返回,变量名不存在。

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

关于这个问题,我们可以从不同的角度来看待作用域在更大的解释框架(运行程序)中的作用。换句话说,假设你正在为一种语言构建一个解释器(或编译器),并负责计算输出,给定一个程序和一些输入。

口译包括跟踪三件事:

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

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

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

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

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

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

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

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

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

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

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

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

假设我们有下面的代码。

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

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

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

词汇范围表示在嵌套的函数组中,内部函数可以访问其父作用域的变量和其他资源

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

词汇范围有时也称为静态作用域

function grandfather() {var name = 'Hammad';// 'likes' is not accessible herefunction parent() {// 'name' is accessible here// 'likes' is not accessible herefunction child() {// Innermost level of the scope chain// 'name' is also accessible herevar likes = 'Coding';}}}

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

但它不会向后工作到它的父级,这意味着变量likes不能被它的父级访问。

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

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

来源

我通常通过例子学习,这里有一点:

const lives = 0;
function catCircus () {this.lives = 1;const lives = 2;
const cat1 = {lives: 5,jumps: () => {console.log(this.lives);}};cat1.jumps(); // 1console.log(cat1); // { lives: 5, jumps: [Function: jumps] }
const cat2 = {lives: 5,jumps: () => {console.log(lives);}};cat2.jumps(); // 2console.log(cat2); // { lives: 5, jumps: [Function: jumps] }
const cat3 = {lives: 5,jumps: () => {const lives = 3;console.log(lives);}};cat3.jumps(); // 3console.log(cat3); // { lives: 5, jumps: [Function: jumps] }
const cat4 = {lives: 5,jumps: function () {console.log(lives);}};cat4.jumps(); // 2console.log(cat4); // { lives: 5, jumps: [Function: jumps] }
const cat5 = {lives: 5,jumps: function () {var lives = 4;console.log(lives);}};cat5.jumps(); // 4console.log(cat5); // { lives: 5, jumps: [Function: jumps] }
const cat6 = {lives: 5,jumps: function () {console.log(this.lives);}};cat6.jumps(); // 5console.log(cat6); // { lives: 5, jumps: [Function: jumps] }
const cat7 = {lives: 5,jumps: function thrownOutOfWindow () {console.log(this.lives);}};cat7.jumps(); // 5console.log(cat7); // { lives: 5, jumps: [Function: thrownOutOfWindow] }}
catCircus();

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

示例:

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

//输出:JavaScript

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

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

foobar总是在可用标识符的词库中,因为它们是全局的。

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

当执行function2时,它可以访问foo3bar3foo2bar2foobar的词典。

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

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

见:

特别感谢@陈志立帮助简化上述定义。

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

默认情况下,JavaScript不会在函数上设置其this的范围(它不会在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"

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

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

在像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没有。您最终可能会嵌套更多的函数。

顺便说一句,在这两个例子中,你还会注意到,即使包含函数的函数已经运行了它的过程,内部词法作用域的变量仍然存在。这称为关闭,指的是嵌套函数对外部变量的访问,即使外部函数已经完成。JavaScript需要足够聪明来确定这些变量是否不再需要,如果不需要,可以垃圾收集它们。

我希望这是有帮助的,这是我对一个稍微抽象的定义的尝试:

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

Fwiw,我这里的逻辑简单地建立在以下定义之上:

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

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

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

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

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

作用域是可访问变量/绑定的上下文。词法作用域表示封闭的词法块或块的本地,而不是例如全局作用域。