什么是词汇作用域简介?
什么是词汇作用域简介?
Answers:
我通过示例了解它们。:)
首先,采用类似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
既可以访问x
的dummy1
或dummy2
,或x
在调用任何函数fun
与x
在其声明。
dummy1();
将打印5
dummy2();
将打印10。
第一个称为静态,因为它可以在编译时推导,第二个称为动态,因为外部作用域是动态的,并且取决于函数的链调用。
我发现静态范围界定对眼睛来说更容易。最终,大多数语言都采用了这种方式,甚至Lisp也是如此(对吗?)。动态作用域就像将所有变量的引用传递给调用的函数一样。
作为为什么编译器无法推断函数外部动态范围的示例,请考虑我们的最后一个示例。如果我们这样写:
if(/* some condition */)
dummy1();
else
dummy2();
调用链取决于运行时条件。如果为true,则调用链如下所示:
dummy1 --> fun()
如果条件为假:
dummy2 --> fun()
fun
两种情况的外部范围都是调用方加上调用方的调用方,依此类推。
只需提及C语言既不允许嵌套函数也不允许动态作用域。
JavaScript
。因此,我认为这不应标记为已接受的答案。JS中的词汇范围有所不同
for
循环内是典型的问题。除非使用ES6 let
或JavaScript,否则JavaScript的词汇范围仅在功能级别上const
。
让我们尝试最短的定义:
词法作用域定义了如何在嵌套函数中解析变量名:内部函数包含父函数的范围,即使父函数已经返回。
这就是全部!
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`
}
//...
据我所知,这些方法在行为上是等效的。
bind
不会影响范围。
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
bind
。有了它们,bind
不再需要。有关此更改的更多信息,请检查stackoverflow.com/a/34361380/11127383
关于词汇和动态作用域的对话中有一个重要的部分丢失了:对范围变量的生存期(或何时可以访问该变量)进行简单说明。
动态作用域仅非常松散地对应于我们传统上考虑的方式的“全局”作用域(我之所以提出两者之间的比较的原因是,它已经被提及了 -我不特别喜欢链接文章的解释); 最好不要在全局变量和动态变量之间进行比较-尽管据链接文章所述,“ ... [它]可以替代全局范围的变量。”
那么,用简单的英语来说,这两种作用域机制之间的重要区别是什么?
在上面的所有答案中,词法作用域的定义非常好:词法范围的变量在定义它的函数的本地级别可用(或可访问)。
但是,由于它不是OP的重点,因此动态作用域还没有引起足够的关注,而它所获得的关注意味着它可能需要更多的关注(这不是对其他答案的批评,而是“哦,这个答案使我们希望还有更多”)。所以,这里还有更多:
动态作用域意味着在函数调用的生命周期内或函数执行期间,较大的程序可以访问变量。确实,维基百科在解释两者之间的差异方面做得很好。为了避免混淆,以下是描述动态作用域的文本:
... [I] n动态范围(或动态范围),如果变量名的范围是某个函数,则其范围是该函数执行的时间段:当函数运行时,变量名存在,并且绑定到其变量,但是在函数返回后,变量名不存在。
词法作用域意味着函数在定义它的上下文中而不是在它周围的范围中查找变量。
如果需要更多细节,请查看词汇范围在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
JavaScript中的词法作用域意味着在函数外部定义的变量可以在变量声明后定义的另一个函数内部访问。但是事实却并非如此。在函数内部定义的变量将无法在该函数外部访问。
这个概念在JavaScript的闭包中大量使用。
假设我们有以下代码。
var x = 2;
var add = function() {
var y = 1;
return x + y;
};
现在,当您调用add()->时,将显示3。
因此,add()函数正在访问x
在方法函数添加之前定义的全局变量。这是由于JavaScript中的词法作用域而引起的。
add()
给定的代码片段之后立即调用该函数,则该函数也会打印3。词法作用域并不仅仅意味着函数可以访问局部上下文之外的全局变量。因此,示例代码确实无助于显示词法作用域的含义。在代码中显示词法作用域确实需要一个反例或至少需要对代码的其他可能解释的解释。
词法范围是指从执行堆栈中的当前位置可见的标识符(例如,变量,函数等)的词库。
- global execution context
- foo
- bar
- function1 execution context
- foo2
- bar2
- function2 execution context
- foo3
- bar3
foo
并且bar
始终在可用标识符的词典之内,因为它们是全局的。
当function1
被执行时,它可以访问的词典foo2
,bar2
,foo
,和bar
。
当function2
被执行时,它可以访问的词典foo3
,bar3
,foo2
,bar2
,foo
,和bar
。
全局和/或外部功能无法访问内部功能标识符的原因是因为尚未执行该功能,因此,没有将其标识符分配给内存。而且,一旦内部上下文执行完毕,就会从执行堆栈中将其删除,这意味着其所有标识符均已被垃圾回收,并且不再可用。
最后,这就是为什么嵌套执行上下文始终可以访问其祖先执行上下文的原因,因此为什么它可以访问更大的标识符词典。
看到:
特别感谢@ robr3rd帮助简化上述定义。
我们可以通过退后一步,看看范围在更大的解释框架(运行程序)中的作用,可以得出这个问题的不同角度。换句话说,假设您正在为一种语言构建解释器(或编译器),并负责计算输出,给定程序和一些输入。
解释涉及跟踪三件事:
状态-即堆和堆栈上的变量和引用的内存位置。
在该状态下的操作-即程序中的每一行代码
给定操作运行的环境 -即状态在操作上的投影。
解释器从程序的第一行代码开始,计算其环境,在该环境中运行该行,并捕获其对程序状态的影响。然后,它遵循程序的控制流程以执行下一行代码,并重复该过程,直到程序结束。
计算任何操作的环境的方式是通过编程语言定义的一组正式规则。术语“绑定”通常用于描述程序的整体状态到环境中的值的映射。请注意,“总体状态”不是指全局状态,而是在执行过程中的任何时候每个可到达的定义的总和。
这是定义范围界定问题的框架。现在到我们的选择的下一部分。
这是动态作用域的要点,其中任何代码运行的环境都绑定到程序的状态,该状态由其执行上下文定义。
换句话说,对于词汇作用域,任何代码所看到的环境都将绑定到与语言中明确定义的作用域(例如,块或函数)相关联的状态。
古老的问题,但这是我的看法。
词法(静态)范围是指源代码中变量的范围。
在像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
使用了内部变量,而buttonB
没有使用。最终嵌套功能可能会超出您的期望。
顺便说一下,在这两个示例中,您还将注意到即使包含函数功能已经运行,内部词法作用域变量仍然存在。这称为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默认情况下不会将其作用域设置为this
on(不会将上下文设置为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"