词汇范围的简要介绍是什么?
当前回答
这是一个古老的问题,但这是我对它的看法。
词法(静态)范围是指源代码中变量的范围。
在JavaScript这样的语言中,函数可以被传递、附加和重新附加到其他对象,您可能会想到,这个范围将取决于当时调用该函数的人,但事实并非如此。以这种方式更改作用域将是动态作用域,而JavaScript不会这样做,除非可能使用此对象引用。
要说明这一点:
var a=“苹果”;函数doit(){var a=“ardvark”;返回函数(){警报(a);}}var test=doit();测试();
在示例中,变量a是全局定义的,但在doit()函数中隐藏。此函数返回另一个函数,如您所见,该函数依赖于自身范围之外的变量。
如果您运行这个,您会发现使用的值是aardwark,而不是apple,虽然它在test()函数的范围内,但不在原始函数的词法范围内。也就是说,使用的范围是源代码中显示的范围,而不是实际使用函数的范围。
这一事实可能会产生令人讨厌的后果。例如,您可能会决定更容易单独组织函数,然后在时间到来时使用它们,例如在事件处理程序中:
var a=“苹果”,b=“香蕉”;函数init(){var a=“ardvark”,b=“andicoot”;document.querySelector('button#a').onclick=函数(事件){警报(a);}document.querySelector('button#b').onclick=doB;}函数doB(事件){警报(b);}init();<button id=“a”>a</button><button id=“b”>b</button>
此代码示例分别执行其中一项。您可以看到,由于词法作用域,按钮A使用内部变量,而按钮B不使用。您可能最终嵌套的函数比您想要的更多。
顺便说一句,在这两个示例中,您还将注意到,即使包含函数函数已经运行,内部词汇范围内的变量仍然存在。这称为闭包,指的是嵌套函数对外部变量的访问,即使外部函数已经完成。JavaScript需要足够聪明,以确定这些变量是否不再需要,如果不需要,可以垃圾收集它们。
其他回答
词汇(AKA静态)作用域是指仅基于变量在代码文本语料库中的位置来确定变量的作用域。变量始终引用其顶级环境。了解它与动态范围的关系是很好的。
范围定义了函数、变量等可用的区域。例如,变量的可用性是在其上下文中定义的,比如函数、文件或对象,它们是在中定义的。我们通常称这些局部变量。
词法部分意味着你可以从阅读源代码中获得范围。
词汇范围也称为静态范围。
动态范围定义了全局变量,这些变量在定义后可以从任何地方调用或引用。有时它们被称为全局变量,尽管大多数编程语言中的全局变量都属于词汇范围。这意味着,可以通过读取代码得出变量在此上下文中可用。也许必须遵循uses或includes子句才能找到实例化或定义,但代码/编译器知道此处的变量。
相反,在动态作用域中,首先搜索本地函数,然后搜索调用本地函数的函数,然后在调用该函数的函数中搜索,依此类推,直到调用堆栈。“动态”是指更改,因为每次调用给定函数时,调用堆栈都可能不同,因此函数可能会根据从何处调用而命中不同的变量。(见此处)
要查看动态范围的有趣示例,请参阅此处。
有关详细信息,请参阅此处和此处。
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('MyClass')的代码将得到结果或不得到结果。对RegisterClass()的调用不必在使用GetClass()单元的词法范围内;
动态范围的另一种可能性是Delphi2009中的匿名方法(闭包),因为它们知道调用函数的变量。它不会递归地遵循调用路径,因此不是完全动态的。
我通常通过举例学习,这里有一点小意思:
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();
JavaScript中的词法作用域意味着在函数外部定义的变量可以在变量声明之后定义的另一个函数内部访问。但事实并非相反;在函数内部定义的变量在该函数外部无法访问。
这个概念在JavaScript中的闭包中大量使用。
假设我们有以下代码。
var x = 2;
var add = function() {
var y = 1;
return x + y;
};
现在,当您调用add()-->时,这将打印3。
因此,add()函数正在访问在方法函数add之前定义的全局变量x。这是由于JavaScript中的词法作用域而调用的。
我喜欢@Arak这样的人提供的功能齐全、语言不可知的答案。由于这个问题被标记为JavaScript,所以我想插入一些与该语言非常相关的注释。
在JavaScript中,我们的作用域选择如下:
按原样(无范围调整)词法var_this=this;函数callback(){console.log(_this);}绑定回调.bind(this)
我认为值得注意的是,JavaScript并没有真正的动态范围。bind调整this关键字,这很接近,但在技术上并不相同。
下面是一个示例,演示了这两种方法。每次决定如何确定回调的范围时,都要这样做,因此这适用于承诺、事件处理程序等。
词汇
以下是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`
}
//...
据我所知,这些方法在行为上是等效的。