前言,如果要问到javaScript的执行顺序。写过javaSacript的开发者们都会有一个直观的印象,就是顺序执行: 如以下代码
var fun1 = function() {
console.log(1);
};
fun1();
var fun2 = function() {
console.log(2);
};
fun2();
// 1
// 2
复制代码
但是真的是这样吗?
function fun() {
console.log(1);
}
fun();
function fun() {
console.log(2);
}
fun();
// 2
// 2
复制代码
执行出来的是两个2。
为什么了?
这个因为javaScript在执行的时候并非一行一行的分析和执行程序,而是一段一段的分析执行。当执行某一段代码的时候,就是做一个“准工作”,比如第一个例子的变量提升和第二个的函数提升。
那这里又有问题,这个一段一段是怎么划分的了。又是在什么情况下才会做“准备工作”了。
可执行代码
引入一个概念,“可执行代码”。javaScript有三种可执行代码:全局代码、函数代码、eval代码。一段一段就是这里的一个一个的可执行代码块。每当执行到一个可执行代码时候,就会进行准备工作,这里的准备工作,就是执行上下文。
执行上下文栈
如果一个脚本中有很多可执行代码,就有很多的执行上下文,那javaSscript引擎是怎么管理这么多的执行上下文的了。这里javaScript会有一个执行上下文栈在管理众多的执行上下文。
举个例子:
funtion fun3 () {
console.log(3);
}
funtion fun2() {
fun3();
}
function fun1() {
fun2();
}
fun1();
复制代码
当执行上诉例子脚本时,首先开始遇到的是全局代码,所以初始化的时候首先会想执行上下文栈(ECStack[])中压入一个全局执行上下文,用globalContext表示,并且只有当整个应用程序执行完毕的时候,ECStack才会被清空。所有ECStack在程序没有执行完毕时,底部永远有一个globalContext。然后我们来执行上诉脚本。
// 执行fun1();
ECStack.push(fun1Context);
// ECStack = [globalContext, fun1Context];
// 把fun1的执行环境压如到执行上下文栈。发现fun1里面引用了fun2.
ECStack.push(fun2Context);
// ECStack = [globalContext, fun1Context, fun2Context];
// 把fun2的执行环境压如到执行上下文栈。发现fun2引用了fun3
ECStack.push(fun3Context);
// ECStack = [globalContext, fun1Context, fun2Context, fun3Context];
// 执行完fun3
ECStack.pop(fun3Context);
// ECStack = [globalContext, fun1Context, fun2Context];
// 执行完fun2
ECStack.pop(fun2Context);
// ECStack = [globalContext, fun1Context];
// 执行完fun1
ECStack.pop(fun1Context);
// ECStack = [globalContext];
// javascript接着执行下面的代码,但是ECStack底层永远有个globalContext
// 程序销毁
ECStack.pop(globalContext);
// ECStack = [];复制代码