这篇文章是一个学习笔记,先以一个宏观的角度考虑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();
总结
- 函数被执行的时候才会创建执行上下文。
- 一个函数一但执行完毕,就会执行出栈操作。
- 一定会有一个全局上下文在栈的底部。