语法分析就是检查你的语法有没有错误,然后预编译简单理解就是在内存中开辟一些空间,存放一些变量与函数,我的理解这个过程中其实包括上下文的创建(当然你可以把全局script代码看成一个自调函数),后面会详细解释清楚,解析执行的意思就是执行代码了!
执行栈,也叫调用栈,它是用来储存函数的执行上上文的,首先执行js代码时,之前我说过你可以把全局script代码看成一个自调函数,此时会创建一个全局执行上下文,然后把这个全局执行上下文推入(push)执行栈中,每当有新的函数调用就会产生新的函数执行上下文,不断的推入执行栈的顶端,根据后进先出(LIFO)的原理,当函数运行完毕后,就会把对应的上下文栈从执行栈中推出(pop),然后上下文控制权将移到当前执行栈的下一个执行上下文。
var a = '全局执行上下文';
function first() {
console.log('执行first函数')
second();
};
function second() {
console.log('执行second函数')
};
first();
执行上下文包括变量对象,this指针,作用域链!
执行上下文有两个阶段:
词法环境:
词法环境类型:
let a = '全局执行上下文';
function b(x) {
var c = 1;
console.log(x + c);
};
b(5);
上面代码修改下,来个简单点的好理解!下面是创建阶段的伪代码:
GlobalExectionContext = {//全局环境
ThisBinding: < Global Object > ,//this指向
LexicalEnvironment: {//词法环境
EnvironmentRecord: {//环境记录
Type: "Object",//全局的类型是Object
a: undefined,//储存a标识符
}
outer: < null >//全局没有外部环境引用
}
};
FunctionExectionContext = {//函数环境
ThisBinding: < Global Object > ,//this指向
LexicalEnvironment: {//词法环境
EnvironmentRecord: {//环境记录
Type: "Declarative",//函数的类型是Declarative
Arguments: { // 储存argumnets标识符
0: 5,
length: 1
},
},
outer: < GlobalLexicalEnvironment >//外部引用是全局环境
},
VariableEnvironment: {//变量环境
EnvironmentRecord: {//环境记录
Type: "Declarative",函数的类型是Declarative
c: undefined//储存c标识符
},
outer: < GlobalLexicalEnvironment >/外部引用是全局环境
}
}
这里就不解释了,注释写的很清楚了!
作用域是定义变量的区域,它有一套访问变量的规则,这套规则用来管理浏览器引擎如何在当前作用域以及嵌套的作用域中根据变量(标识符)进行变量查找
执行上下文有两个阶段:
在函数上下文中,用活动对象(activation object, AO)来表示变量对象,变量对象(VO)是规范上或者是JS引擎上实现的,并不能在JS环境中直接访问,当进入到一个执行上下文后,这个变量对象才会被激活,所以叫活动对象(AO),这时候活动对象上的各种属性才能被访问!
[[Scope]]属性是函数创建时产生的,它是当前的作用域对象,也就是函数的标识符被创建的时候,我们所能够直接访问的那个作用域对象,它指向上级(父级的)作用域对象,当创建自己的活动对象|变量对象时,会把这个执行环境的[[scope]]按顺序复制到[[scope chain]]里,最后把这个活动对象推入到[[scope chain]]的顶部。这样[[scope chain]]就是一个有序的栈,这样保了对执行环境有权访问的所有变量和对象的有序访问,另外,[[scope]]中所存储执行期上下文对象的集合,这个集合呈链式连接,我们把这种链式连接叫作用域链,下面来看下代码的创建阶段并附带伪代码
<script>
var a = 1;
function b(x) {
var c = 10;
function d() {
console.log('d函数执行');
};
d();
};
b(100);
</script>
首先创建一个全局的对象Global object(window),然后语法分析检查语法有没有错误,然后开始预编译(创建上下文),这个时候会创建活动对象!
//伪代码实现
//window.AO (window活动对象) document,navigator等属性我这里就不一一写了
global.AO: {
a: undefined,
b: function b(x) {
var c = 10;
function d() {
console.log('d函数执行');
};
d();
}
}
//global.[[scope]]===null
global.[[scope chain]]=[global.AO]//因为全局没有外部环境引用(直接把自己推入到[[scope chain]]的顶部)
创建完全局上下文之后就Push到当前的执行栈中,然后开始进行代码执行,执行过程中进行变量赋值,这个时候b函数还没有执行!
//伪代码实现
//执行阶段进行变量赋值
global.AO: {
a: 1,
b: function b(x) {
var c = 10;
function d() {
console.log('d函数执行');
};
d();
}
}
// 此时全局上下文 之前说过上下文包括变量对象,this指针,作用域链
//此时变量对象已经被激活成活动对象了(创建阶段就被激活了)
global.EC={
this:window,
AO: global.AO.//这里不重复写了,
scope chain:[ global.AO]// global.[[scope chain]]
}
然后执行到b函数,同样上面的类似操作,首先创建上下文阶段并压入执行栈!
//活动对象
b.AO = {
x:10//形参赋值
c: undefined,
d: function d() {
console.log('d函数执行');
}
}
执行阶段进行变量赋值!
b.AO = {
x:100
c: 10,
d: function d() {
console.log('d函数执行');
}
};
// b[[scope]]===global.[[scope chain]]
b.[[scope chain]]=[b.AO ,global.AO]//这里需要注意的是,先复制b[[scope]]===global.[[scope chain]],然后把b.AO 放在作用域链的最顶端,这个就是b的作用域链
此时b函数的上下文
// 此时全局上下文 之前说过上下文包括变量对象,this指针,作用域链
//此时变量对象已经被激活成活动对象了(创建阶段就被激活了)
b.EC={
this:window,//因为是window调用 b()===window.b();
AO: b.AO.//这里不重复写了,
scope chain:[b.AO ,global.AO] // b.[[scope chain]]
}
然后执行到d函数,同样上面的类似操作,首先创建上下文阶段并压入执行栈!然后再执行阶段,这里d函数里面没有变量声明和函数声明,我这里就不写了!这里我模拟下d的执行上下文!
d.context={
this:window,//因为是window调用 d()===window.d();
AO: d.AO.//这里不重复写了,
scope chain:[d.AO,b.AO ,global.AO]//先把b.[[scope]]===[b.AO ,global.AO] 复制过来,然后把d.AO放到作用域的最顶端!
}
执行完d函数之后,从执行栈中弹出(后进先出),上下文控制权将交给下一个执行上下文!然后不断的重复!最后只留下一个全局上下文,当浏览器关闭时候出栈!我这个例子比较简单!但是看完之后应该可以很清楚的了解上下文的执行,作用域和作用域链它们之前的关系!
创建一个全局执行上下文并Push到当前的执行栈中!
创建全局对象并将将全局对象压入作用域链!
执行全局上下文进行变量赋值,函数引用,执行代码等操作!
创建函数上下文并Push到当前的执行栈中!
创建函数活动对象,并用 arguments 创建活动对象!
为当前创建[[Scope]]属性,并将复制其上层(父级)作用域链保存到该属性!
将当前活动对象压入当前上下文中的作用域链最顶端!
执行函数,然后重复函数创建执行等操作!
函数执行完上下文从执行栈的顶端移除,并且变量对象随之销毁!
所有函数执行完之后执行栈中只剩下全局上下文,在应用关闭时销毁!
参考文献:JavaScript深入理解之作用域链