Home  >  Article  >  Web Front-end  >  JavaScript lexical scope and closure analysis explanation_javascript skills

JavaScript lexical scope and closure analysis explanation_javascript skills

WBOY
WBOYOriginal
2016-05-16 18:21:281017browse
Copy code The code is as follows:

var classA = function(){
this.prop1 = 1 ;
}
classA.prototype.func1 = function(){
var that = this,
var1 = 2;

function a(){
return function( ){
alert(var1);
alert(this.prop1);
}.apply(that);
};
a();
}
var objA = new ClassA();
objA.func1();

You should have written code similar to the above. In fact, what I want to express here is that sometimes a method is defined It will be thousands of miles away from the place where it is used. When the method is executed, which variables can it access and which variables cannot it access? How to judge this? This is the issue we need to analyze this time - lexical scope

Lexical scope: The scope of a variable is determined when it is defined rather than when it is executed. That is to say, the lexical scope depends on the source code, through static Analysis can determine it, so lexical scope is also called static scope. Except for with and eval, it can only be said that the scope mechanism of JS is very close to lexical scope (Lexical scope).


Through a few small cases, we will begin to gain an in-depth understanding of some underlying concepts and theoretical knowledge during JS execution that are essential for understanding lexical scope and closure.

Reappearance of classic cases
1. Classic case 1
Copy code The code is as follows:

/*A piece of code under the global (window) domain*/
function a(i) {
var i;
alert(i);
};
a(10);

Question: What will the above code output?
Answer: Yes, 10 pops up. The specific execution process should be like this

The a function has a formal parameter i. When calling the a function, the actual parameter 10 is passed in, and the formal parameter i=10
Then a local variable i with the same name is defined, which is not assigned a value.
alert output 10
Thinking: Are local variable i and formal parameter i the same storage space?
2. Classic Case 2
Copy the code The code is as follows:

/*A piece of code under the global (window) domain*/
function a(i) {
alert(i);
alert(arguments[0]); //arguments[0] should It is the formal parameter i
var i = 2;
alert(i);
alert(arguments[0]);
};
a(10);

Question: What will the above code output? ((10,10,2,10 10,10,2,2))
Answer: The running result in FireBug is the second 10,10,2,2, you guessed it..., let’s briefly talk about it Specific execution process

The a function has a formal parameter i. When calling the a function, the actual parameter 10 is passed in, and the formal parameter i=10
The first alert outputs the value 10 of the formal parameter i
The second alert outputs arguments[0], which should also be i
Then define a local variable i and assign it a value of 2. At this time, the local variable i=2
The third alert sets the value of the local variable i to 2 Output
The fourth alert outputs arguments[0] again
Thinking: Can this explain that the values ​​of local variable i and formal parameter i are the same?
3. Classic Case 3
Copy the code The code is as follows:

/*A piece of code under the global (window) domain*/
function a(i) {
var i = i;
alert(i);
};
a( 10);

Question: What will the above code output? (( undefined 10 ))
Answer: The running result in FireBug is 10. Let’s briefly talk about the specific execution process

The first sentence declares a local variable i with the same name as the formal parameter i. According to the result We know that the latter i points to the
formal parameter i, so here it is equivalent to assigning the value 10 of the formal parameter i to the local variable i
The second alert will of course output 10
Thinking: Combination case Column 2, this basically shows that the local variable i and the formal parameter i point to the same storage address!
4. Classic Case 4
Copy the code The code is as follows:

/*A piece of code under the global (window) domain*/
var i=10;
function a() {
alert(i);
var i = 2;
alert(i);
};
a();

疑问:上面的代码又会输出什么呢?(小子,看这回整不死你!哇哈哈,就不给你选项)
答案:在FireBug中的运行结果是 undefined, 2,下面简单说一下具体执行过程

第一个alert输出undefined
第二个alert输出 2
思考:到底怎么回事儿?
5、经典案例五…………..N
看到上面的几个例子,你可能会想,怎么可能,我写了几年的 js 了,怎么这么简单例子也会犹豫,结果可能还答错了。其实可能原因是:我们能很快的写出一个方法,但到底方法内部是怎么执行的呢?执行的细节又是怎么样的呢?你可能没有进行过深入的学习和了解。要了解这些细节,那就需要了解 JS 引擎的工作方式,所以下面我们就把 JS 引擎对一个方法的解析过程进行一个稍微深入一些的介绍

解析过程

1、执行顺序

  • 编译型语言,编译步骤分为:词法分析、语法分析、语义检查、代码优化和字节生成。
  • 解释型语言,通过词法分析和语法分析得到语法分析树后,就可以开始解释执行了。这里是一个简单原始的关于解析过程的原理,仅作为参考,详细的解析过程(各种JS引擎还有不同)还需要更深一步的研究

JavaScript执行过程,如果一个文档流中包含多个script代码段(用script标签分隔的js代码或引入的js文件),它们的运行顺序是:

  1. 步骤1. 读入第一个代码段(js执行引擎并非一行一行地执行程序,而是一段一段地分析执行的)
  2. 步骤2. 做词法分析和语法分析,有错则报语法错误(比如括号不匹配等),并跳转到步骤5
  3. 步骤3. 对【var】变量和【function】定义做“预解析“(永远不会报错的,因为只解析正确的声明)
  4. 步骤4. 执行代码段,有错则报错(比如变量未定义)
  5. 步骤5. 如果还有下一个代码段,则读入下一个代码段,重复步骤2
  6. 步骤6. 结束

2、特殊说明
全局域(window)域下所有JS代码可以被看成是一个“匿名方法“,它会被自动执行,而此“匿名方法“内的其它方法则是在被显示调用的时候才被执行
3、关键步骤
上面的过程,我们主要是分成两个阶段

  1. 解析:就是通过语法分析和预解析构造合法的语法分析树。
  2. 执行:执行具体的某个function,JS引擎在执行每个函数实例时,都会创建一个执行环境(ExecutionContext)和活动对象(activeObject)(它们属于宿主对象,与函数实例的生命周期保持一致)

3、关键概念
到这里,我们再更强调以下一些概念,这些概念都会在下面用一个一个的实体来表示,便于大家理解

  • 语法分析树(SyntaxTree)可以直观地表示出这段代码的相关信息,具体的实现就是JS引擎创建了一些表,用来记录每个方法内的变量集(variables),方法集(functions)和作用域(scope)等
  • 执行环境(ExecutionContext)可理解为一个记录当前执行的方法【外部描述信息】的对象,记录所执行方法的类型,名称,参数和活动对象(activeObject)
  • 活动对象(activeObject)可理解为一个记录当前执行的方法【内部执行信息】的对象,记录内部变量集(variables)、内嵌函数集(functions)、实参(arguments)、作用域链(scopeChain)等执行所需信息,其中内部变量集(variables)、内嵌函数集(functions)是直接从第一步建立的语法分析树复制过来的
  • 词法作用域:变量的作用域是在定义时决定而不是执行时决定,也就是说词法作用域取决于源码,通过静态分析就能确定,因此词法作用域也叫做静态作用域。 with和eval除外,所以只能说JS的作用域机制非常接近词法作用域(Lexical scope)
  • 作用域链:词法作用域的实现机制就是作用域链(scopeChain)。作用域链是一套按名称查找(Name Lookup)的机制,首先在当前执行环境的 ActiveObject 中寻找,没找到,则顺着作用域链到父 ActiveObject 中寻找,一直找到全局调用对象(Global Object)

4、实体表示
源码,语法分析树,执行环境和活动对象的引用关系

Analytical Simulation

I guess, after seeing this, everyone is still confused. What is a parse tree, what does a parse tree look like, how is the scope chain implemented, what is the content of the active object, etc. It is still not too clear. , below we will simulate the entire parsing process through a piece of actual code. We will actually create the syntax parse tree and active objects, understand the scope, and how the scope chain is implemented

1. Simulation code
Copy code The code is as follows:

/*Under the global (window) domain A piece of code*/
var i = 1,j = 2,k = 3;
function a(o,p,x,q){
var x = 4;
alert(i );
function b(r,s) {
var i = 11,y = 5;
alert(i);
function c(t){
var z = 6;
alert(i);
};
//Function expression
var d = function(){
alert(y);
};
c(60 );
d();
};
b(40,50);
}
a(10,20,30);

2 , Parse tree
The above code is very simple. It first defines some global variables and global methods, and then defines local variables and local methods within the method. Now the JS interpreter reads this code and starts parsing it. As mentioned earlier The JS engine will first obtain the parse tree through syntax analysis and pre-parsing. As for what the syntax parse tree looks like and what information it contains, below we use a simple structure: a JS object (in order to clearly represent various objects (what is here is just a pseudo-object representation and may not run) to describe the syntax parse tree (this is something we are more familiar with. We will not delve into the actual structure. It must be much more complicated. This is specifically to help understand the parsing process. Simplified)
Copy code The code is as follows:

/**
* Simulate the establishment of a syntax analysis tree and store the variables and methods in the function
*/
var SyntaxTree = {
// Representation of global objects in the syntax parse tree
window: {
variables:{
i:{ value:1},
j:{ value: 2},
k:{ value:3}
},
functions:{
a: this.a
}
},

a:{
variables:{
x:"undefined"
},
functions:{
b: this.b
},
scope: this.window
} ,

b:{
variables:{
y:"undefined"
},
functions:{
c: this.c,
d: this .d
},
scope: this.a
},

c:{
variables:{
z:"undefined"
},
functions:{},
scope: this.b
},

d:{
variables:{},
functions:{},
scope: {
myname:d,
scope: this.b
}
}
};

The above is a simple representation of the parse tree, as we As analyzed earlier, the parse tree mainly records the variable set (variables), method set (functions) and scope (scope) in each function
Key points of the parse tree

1 variable set ( variables), there are only variable definitions but no variable values. At this time, the variable values ​​​​are all "undefined"
2 scope (scope). According to the characteristics of lexical scope, the scope of each variable is already clear at this time. , and will not change with the execution environment. 【What does it mean? That is, we often return a method and then execute it in another method. When executed, the scope of the variables in the method is based on the scope when the method is defined. In fact, what I want to express here is that no matter how complex or remote you execute the method, the final judgment of whether the variables in the method can be accessed still has to go back to the place where the method was defined to verify]
3 Scope (scope) Establishing rules
a For function declarations and anonymous function expressions, [scope] is the scope when it is created
b For named function expressions, the top of [scope] is a new JS object ( That is, it inherits Object.prototype). This object has two properties. The first is its own name, and the second is the defined scope. The first function name is to ensure that the code inside the function can access itself without error. function name recursively.
3. Execution environment and active objects
The syntax analysis is completed and code execution begins. When we call each method, the JS engine will automatically establish an execution environment and an active object for it. They are consistent with the life cycle of the method instance and provide necessary execution support for method execution. For the above methods, we Here, an active object is uniformly established for it (theoretically, the active object is generated when the method is executed. For the convenience of demonstration, the active objects of all methods are defined here at once), as follows:
Execution Environment
Copy code The code is as follows:

/**
* Execution environment: The execution environment created when the function is executed
*/
var ExecutionContext = {
window: {
type: "global",
name: "global",
body: ActiveObject.window
},

a:{
type: "function",
name: "a",
body: ActiveObject.a,
scopeChain: this .window.body
},

b:{
type: "function",
name: "b",
body: ActiveObject.b,
scopeChain: this.a.body
},

c:{
type: "function",
name: "c",
body: ActiveObject.c,
scopeChain : this.b.body
},

d:{
type: "function",
name: "d",
body: ActiveObject.d,
scopeChain: this.b.body
}
}

The execution environment of each of the above methods stores the corresponding method type (function), method name (funcName), and active object (ActiveObject), scope chain (scopeChain) and other information, the key points are as follows:

body attribute, directly points to the active object of the current method
scopeChain attribute, scope chain, which is a linked list structure, According to the scope attribute corresponding to the current method in the parse tree, it points to the active object (ActivceObject) of the method corresponding to the scope. The variable search is to follow this chain to find the
active object
Copy code The code is as follows:

/**
* Active objects: list of active objects created when the function is executed
*/
var ActiveObject = {
window: {
variables:{
i: { value:1},
j: { value:2},
k: { value:3}
},
functions:{
a: this.a
}
},

a:{
variables:{
x: {value:4}
},
functions:{
b: SyntaxTree.b
},
parameters:{
o: {value: 10},
p: {value: 20},
x: this.variables. x,
q: "undefined"
},
arguments:[this.parameters.o,this.parameters.p,this.parameters.x]
},

b:{
variables:{
y:{ value:5}
},
functions:{
c: SyntaxTree.c,
d: SyntaxTree.d
},
parameters:{
r:{value:40},
s:{value:50}
},
arguments:[this.parameters.r,this.parameters. s]
},

c:{
variables:{
z:{ value:6}
},
functions:{},
parameters: {
u:{value:70}
},
arguments:[this.parameters.u]
},

d:{
variables:{},
functions:{},
parameters:{},
arguments:[]
}
}

Each of the above active objects stores corresponding methods The internal variable set (variables), embedded function set (functions), formal parameters (parameters), actual parameters (arguments) and other information required for execution, the key points of the active object

create the active object, from the syntax analysis The internal variable set (variables) and embedded function set (functions) of the tree copy method
method starts to execute, and the internal variable set in the active object is all reset to undefined
Create formal parameters (parameters) and actual parameters (arguments) objects, actual parameters with the same name, formal parameters and variables are [reference] relationships
Execute the assignment statement in the method, then the variables in the variable set will be assigned values ​​
The variable search rule is first Search in the ActiveObject of the current execution environment. If not found, then search in the ActiveObject pointed to by the property ScopeChain in the execution environment until the Global Object (window)
method is executed. The internal variable value will not be reset. As for when the variable will be destroyed, please refer to the following
The life cycle of the variable within the method depends on whether there is an active reference to the method instance. If not, the active object will be destroyed
6 and 7 enable the closure to access external variables The root cause of The processing is that the variables and formal parameters with the same name refer to the same memory address], so there will be a situation where the modified arguments in the second case will affect the local variables

Case 4

According to [JS Engine variable search rules, first search in the ActiveObject of the current execution environment, if not found, then search along the ActiveObject pointed to by the property ScopeChain in the execution environment, all the way to the Global Object (window)], so in the fourth, because in the current The definition of variable i was found in ActiveObject, but the value was "undefined", so "undefined" was output directly

Summary
The above is what I learned and used JS for a period of time, in order to go deeper In order to better understand it, and to better grasp its application, in the process of learning closures, I have some understanding and summary of lexical scope. There may be some differences between them and the real JS interpretation engine. Because I am only analyzing this issue from the perspective of a new front-end developer rather than a system designer. I hope it can bring some help to JS developers in understanding the scope of this method!
Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn