这篇文章是一个学习笔记,先以一个宏观的角度考虑JS 引擎的执行过程,方便之后学习执行上下文。
JS 是怎么执行代码的
与大家想的可能有些不太一样,JS 代码的执行并非是按照顺序一行一行执行的。而是以块
为单位来执行的。
那么问题来了,这个 块
是啥?JS 引擎是怎么区分这些块
的?
JS 可执行代码
先回答第一个问题,上文说的块
有三种:
- 全局代码
- 函数代码
eval
代码
这三种代码都是我们所谓的块
。而每次 JS 引擎遇到这种块,就会进行分析,这个过程也就是创建执行上下文(执行环境)的过程。
执行上下文栈
我们知道,一个完整的程序,也许是充满了各种函数(先不不考虑变量),换句话说,完整的程序中可能会有很多的执行上下文。为了方便 JS 引擎进行管理,需要有一个栈来管理这些执行上下文,这个栈就是执行上下文栈(ECStack => Execution content Stack
)。
现在可以解释一下第二个问题。JS 引擎在创建执行上下文的时候,会默认在栈(ECStack)
的底部,先 push
一个全局上下文
,因为每个程序都有一个全局代码,接下来每识别到一个函数,也就是函数代码
,就会创建一个执行上下文
,并继续 push
到 栈中。
入栈规则
- 一个完整的程序在执行过程中,一开始入栈的一定是全局上下文,因此栈的底部就是
globalContext
。 - 接下来每遇到一个被执行函数,就会创建一个执行上下文。
- 如果前一个函数中还有函数被执行,会继续push新的执行上下文。
- 当函数执行完成之后,会把刚执行完的上下文进行
pop
操作。
Demo1
function fun3() { console.log('fun3'); }
function fun2() { fun3(); }
function fun1() { fun2(); }
fun1();
分析:
- 按照规则,栈的底部一定是一个全局上下文。
ECStack.push(globalContext);
- 遇到第一个执行的函数是
fun1
,入栈。
ECStack.push(fun1);
- 在
fun1
上下文中执行的过程中,fun2
被执行,入栈。
ECStack.push(fun2);
- 在
fun2
上下文中执行的过程中,fun3
被执行,入栈。
ECStack.push(fun3);
fun3
执行完毕,出栈。
ECStack.pop();
ECStack.length == 3
fun2
执行完毕,出栈。
ECStack.pop();
ECStack.length == 2
fun1
执行完毕,出栈。
ECStack.pop();
ECStack.length == 1
- 程序结束,全局上下文出栈。
ECStack.pop();
ECStack.length == 0
Demo2
在开始前,可以先考虑一下这两个片段入栈
的情况。
// ------------ 片段1 ------------
function fun1() {
var value = 'scope';
function fun2() {
return value;
}
return fun2();
}
fun1();
// ------------ 片段2 ------------
function fun1() {
var value = 'scope';
function fun2() {
return value;
}
return fun2;
}
fun1()();
片段 1
偷懒了,画图好麻烦。省略号表示一直出栈直到清空。
- 按照规则,栈的底部一定是一个全局上下文。
ECStack.push(globalContext);
- 遇到第一个执行的函数是
fun1
,入栈。
ECStack.push(fun1);
- 在
fun1
执行return
的时候,fun2
被执行,入栈。
ECStack.push(fun2);
fun2
执行结束,出栈。
ECStack.pop();
fun1
执行结束,出栈。
ECStack.pop();
- 全部执行结束,
globalContext
出栈。
ECStack.pop();
片段 2
- 按照规则,栈的底部一定是一个全局上下文。
ECStack.push(globalContext);
- 遇到第一个执行的函数是
fun1
,入栈。
ECStack.push(fun1);
- 在
fun1
执行return
的时候,fun2
未被执行,返回函数,fun1
出栈。
ECStack.pop();
fun2
执行,入栈。
ECStack.push(fun2);
fun2
执行结束,出栈。
ECStack.pop();
- 全部执行结束,
globalContext
出栈。
ECStack.pop();
总结
- 函数被执行的时候才会创建执行上下文。
- 一个函数一但执行完毕,就会执行出栈操作。
- 一定会有一个全局上下文在栈的底部。