什么是词汇作用域简介?
什么是词汇范围?
我通常通过示例学习,这里有一些内容:
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();
词汇范围是指从执行堆栈中的当前位置可见的标识符(例如,变量,函数等)的词典。
- global execution context
- foo
- bar
- function1 execution context
- foo2
- bar2
- function2 execution context
- foo3
- bar3
foo
and bar
are always within the lexicon of available identifiers because they are global.
When function1
is executed, it has access to a lexicon of foo2
, bar2
, foo
, and bar
.
When function2
is executed, it has access to a lexicon of foo3
, bar3
, foo2
, bar2
, foo
, and bar
.
The reason global and/or outer functions do not have access to an inner functions identifiers is because the execution of that function has not occurred yet and therefore, none of its identifiers have been allocated to memory. What’s more, once that inner context executes, it is removed from the execution stack, meaning that all of it’s identifiers have been garbage collected and are no longer available.
最后,这就是为什么嵌套执行上下文始终可以访问其祖先执行上下文的原因,因此为什么它可以访问更大的标识符词典。
看到:
- https://tylermcginnis.com/ultimate-guide-to-execution-contexts-hoisting-scopes-and-closures-in-javascript/
- https://developer.mozilla.org/zh-CN/docs/Glossary/Identifier
特别感谢@ robr3rd帮助简化上述定义。
我们可以通过退后一步,看看范围在更大的解释框架(运行程序)中的作用,可以得出这个问题的不同角度。换句话说,假设您正在为一种语言构建解释器(或编译器),并负责计算输出,给定程序和一些输入。
解释涉及跟踪三件事:
状态-即堆和堆栈上的变量和引用的内存位置。
在该状态下的操作-即程序中的每一行代码
给定操作运行的环境 -即状态在操作上的投影。
解释器从程序的第一行代码开始,计算其环境,在该环境中运行该行,并捕获其对程序状态的影响。然后,它遵循程序的控制流程以执行下一行代码,并重复该过程,直到程序结束。
计算任何操作的环境的方式是通过编程语言定义的一组正式规则。术语“绑定”通常用于描述程序的整体状态到环境中的值的映射。请注意,“总体状态”不是指全局状态,而是在执行过程中的任何时候每个可到达的定义的总和。
这是定义范围问题的框架。现在到我们的选择的下一部分。
- 作为解释器的实现者,您可以通过使环境尽可能接近程序的状态来简化任务。因此,代码行的环境将简单地由前一行代码的环境定义,并对其施加操作的效果,而不论前一行是否是赋值,函数调用,从函数返回,或诸如while循环之类的控制结构。
这是动态作用域的要旨,其中任何代码在其中运行的环境都绑定到程序的状态,该状态由其执行上下文定义。
- 或者,您可能会想到程序员使用您的语言,并简化了他或她跟踪变量可以采用的值的任务。关于结果的推理和过去执行的总过程涉及太多的路径和太多的复杂性。Lexical Scoping通过将当前环境限制在当前块,函数或其他作用域单位及其父级(即,包含当前时钟的块或称为当前函数的函数)中定义的状态部分来帮助实现此目的。
换句话说,对于词汇作用域,任何代码所看到的环境都将绑定到与语言中明确定义的作用域(例如,块或函数)相关联的状态。
JavaScript中的词法作用域意味着在函数外部定义的变量可以在变量声明后定义的另一个函数内部访问。但是事实却并非如此。函数内部定义的变量将无法在该函数外部访问。
这个概念在JavaScript的闭包中大量使用。
假设我们有以下代码。
var x = 2;
var add = function() {
var y = 1;
return x + y;
};
现在,当您调用add()->时,将显示3。
因此,add()函数正在访问x
在方法函数添加之前定义的全局变量。这是由于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动态作用域(或动态范围),如果变量名的范围是某个函数,则其范围是该函数执行的时间段:函数运行时,变量名存在,并且绑定到其变量,但是在函数返回后,变量名不存在。
用简单的语言来说,词法作用域是在作用域之外定义的变量,或者上限作用域在作用域内部自动可用,这意味着您不需要将其传递到那里。
例:
let str="JavaScript";
const myFun = () => {
console.log(str);
}
myFun();
//输出:JavaScript
词法作用域:在函数外部声明的变量是全局变量,并且在JavaScript程序中随处可见。在函数内部声明的变量具有函数作用域,并且仅对显示在该函数内部的代码可见。
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静态)作用域是指仅根据变量在代码文本语料库中的位置来确定变量的范围。变量始终引用其顶层环境。最好将其与动态范围相关联。
让我们尝试最短的定义:
词法作用域定义了如何在嵌套函数中解析变量名:内部函数包含父函数的范围,即使父函数已经返回。
这就是全部!
我通过示例了解它们。:)
首先,采用类似C的语法的词汇作用域(也称为静态作用域):
void fun()
{
int x = 5;
void fun2()
{
printf("%d", x);
}
}
每个内部级别都可以访问其外部级别。
There is another way, called dynamic scope used by the first implementation of Lisp, again in a C-like syntax:
void fun()
{
printf("%d", x);
}
void dummy1()
{
int x = 5;
fun();
}
void dummy2()
{
int x = 10;
fun();
}
Here fun
can either access x
in dummy1
or dummy2
, or any x
in any function that call fun
with x
declared in it.
dummy1();
will print 5,
dummy2();
will print 10.
The first one is called static because it can be deduced at compile-time, and the second is called dynamic because the outer scope is dynamic and depends on the chain call of the functions.
I find static scoping easier for the eye. Most languages went this way eventually, even Lisp (can do both, right?). Dynamic scoping is like passing references of all variables to the called function.
As an example of why the compiler can not deduce the outer dynamic scope of a function, consider our last example. If we write something like this:
if(/* some condition */)
dummy1();
else
dummy2();
The call chain depends on a run time condition. If it is true, then the call chain looks like:
dummy1 --> fun()
If the condition is false:
dummy2 --> fun()
The outer scope of fun
in both cases is the caller plus the caller of the caller and so on.
Just to mention that the C language does not allow nested functions nor dynamic scoping.
本主题与内置
bind
函数密切相关,并在ECMAScript 6 Arrow Functions中引入。确实很烦人,因为对于我们要使用的每个新“类”(实际上是函数)方法,我们必须bind
这样做才能访问范围。JavaScript默认情况下不会将其作用域设置为
this
on(它不会将上下文设置为onthis
)。默认情况下,您必须明确地说出您想要的上下文。的箭头功能自动获取所谓的词法作用域(访问变量的定义在它的包含块)。使用箭头功能时,它将自动绑定
this
到最初定义箭头功能的位置,并且此箭头功能的上下文为其包含块。在下面最简单的示例中查看其在实践中的工作方式。
在箭头函数之前(默认情况下没有词法范围):
带箭头功能(默认为词法作用域):