什么是词法作用域的简要介绍?
什么是词法作用域的简要介绍?
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
可以访问 dummy1
或者 dummy2
中的 x
,或者在调用带有声明 x
的任何函数时,可以访问其中的任何 x
。dummy1();
将会打印出5,
dummy2();
将会打印出10。
第一个被称为静态是因为它可以在编译时推断出来,而第二个被称为动态是因为外部作用域是动态的,并且取决于函数链调用。
我发现静态作用域更容易理解。大多数语言最终都采用了这种方式,即使Lisp也是如此(可以同时进行吧?)。动态作用域就像将所有变量的引用传递给被调用的函数。
作为为什么编译器无法推断出函数的外部动态作用域的示例,请考虑我们的最后一个示例。如果我们写出类似这样的东西:
if(/* some condition */)
dummy1();
else
dummy2();
调用链依赖于运行时条件。如果条件为真,则调用链如下:
dummy1 --> fun()
dummy2 --> fun()
fun
的外部范围是调用者以及调用者的调用者等等。JavaScript
。因此,我认为这不应该被标记为被接受的答案。特别是在JS中,词法作用域是不同的。 - Boyangfor
循环内部是典型的问题。JavaScript的词法作用域仅限于函数级别,除非使用ES6的let
或const
。 - icc97让我们尝试最简短的定义:
词法作用域 定义了在嵌套函数中如何解析变量名:即使父函数已经返回,内部函数仍然包含父函数的作用域。
就是这样简单!
var scope = "I am global";
function whatismyscope(){
var scope = "I am just a local";
function func() {return scope;}
return func;
}
whatismyscope()()
上述代码将返回"I am just a local"。它不会返回"I am a global",因为函数func()计算的是它最初定义的位置,即函数whatismyscope的范围内。无论它被调用时处于什么状态(全局作用域/来自另一个函数甚至),它都不会受到干扰,这就是为什么全局作用域值"I am global"不会被打印出来。这被称为词法作用域,其中"函数使用在其定义时有效的作用域链被执行" -根据JavaScript定义指南。词法作用域是一个非常强大的概念。作用域定义了函数、变量等可用的范围。例如,变量的可用性在其上下文中(比如函数、文件或对象)定义。我们通常将这些称为局部变量。
词法部分意味着您可以从源代码中推导出作用域。
词法作用域也被称为静态作用域。
动态作用域定义了可以在定义后从任何地方调用或引用的全局变量。有时它们被称为全局变量,尽管大多数编程语言中的全局变量都是具有词法作用域的。这意味着可以通过阅读代码推导出变量在此上下文中可用。也许需要遵循使用或包含子句以查找实例化或定义,但是代码/编译器知道变量在此位置。
相比之下,动态作用域首先在本地函数中搜索,然后在调用本地函数的函数中搜索,然后在调用该函数的函数中搜索,依此类推,一直到呼叫栈的最上层。 “动态”指的是更改,因为每次调用给定的函数时,调用堆栈可能是不同的,因此该函数可能会命中不同的变量,具体取决于它的调用来源。(请参见这里)
有关动态作用域的有趣示例,请参见此处。
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')的代码将会得到一个结果或者不会得到任何结果。RegisterClass()的调用不必在使用GetClass()的单元的词法范围内。
另一种实现动态作用域的可能性是Delphi 2009中的匿名方法(闭包),因为它们知道其调用函数的变量。它不会从那里递归地跟踪调用路径,因此不完全是动态的。
词法作用域指的是在一个嵌套的函数组中,内部函数可以访问其父级作用域中的变量和其他资源。
这意味着子函数与其父函数的执行上下文词法绑定。
词法作用域有时也称为静态作用域。
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';
}
}
}
关于词法作用域(lexical scope)的一点就是它向前工作,这意味着名称可以通过其子执行上下文进行访问。
但它不向后工作到其父级,这意味着变量likes
不能被其父级访问。
这也告诉我们,在不同执行上下文中具有相同名称的变量从执行堆栈的顶部向下获得优先权。
具有与另一个变量类似的名称的变量,在最内层函数(执行堆栈的最高上下文)中将具有更高的优先级。
来源。
JavaScript中的词法作用域意味着在函数外定义的变量可以在该变量声明后定义的另一个函数内访问。但反过来则不成立:在函数内定义的变量将无法在该函数外访问。
这个概念在JavaScript中的闭包中被广泛使用。
假设我们有以下代码。
var x = 2;
var add = function() {
var y = 1;
return x + y;
};
现在,当您调用add() --> 这将打印3。
因此,add()函数正在访问在方法函数add之前定义的全局变量x
。这是由JavaScript中的词法作用域引起的。
add()
函数,则它也会打印3。词法作用域并不仅意味着函数可以访问局部上下文之外的全局变量。因此,示例代码实际上并没有帮助展示词法作用域的含义。在代码中展示词法作用域真正需要一个反例或至少解释代码的其他可能解释。 - C Perkins我喜欢像 @Arak 这样的专业、不依赖于具体编程语言的回答。不过,既然这个问题被标记为了 JavaScript,那么我想贡献一些非常特定于这种语言的笔记。
在 JavaScript 中,我们的作用域选择有:
var _this = this; function callback(){ console.log(_this); }
callback.bind(this)
值得注意的是,我认为 JavaScript 实际上并没有动态作用域。 .bind
调整了 this
关键字,这很接近,但在技术上并不完全相同。
下面是一个演示两种方法的示例。每当您决定如何作用域回调函数时,都需要这样做,因此这适用于 promises、事件处理程序等。
下面展示的是 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
不会影响作用域。 - Ben Aston简单地说,词法作用域是指在您的作用域之外或上层作用域中定义的变量会自动在您的作用域中可用,这意味着您不需要将其传递到那里。
例子:
let str="JavaScript";
const myFun = () => {
console.log(str);
}
myFun();
// 输出:JavaScript
bind
的问题。使用它们时,不再需要bind
。有关此更改的更多信息,请访问https://dev59.com/7FsX5IYBdhLWcg3wALXa#34361380。 - Daniel Danielecki词法作用域意味着函数在定义它的上下文中查找变量,而不是在其周围的范围内查找。
如果您想了解词法作用域在Lisp中的工作原理,请查看Kyle Cronin在Common Lisp中的动态和词法变量中的选定答案,它比这里的答案清晰得多。
巧合的是,我只是在一节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