欢迎关注微信公众号:前端阅读室
顺序执行?
我们对 JavaScript 执行的直观印象是顺序执行,比如下面这段代码:
var foo = function() {
console.log("foo1");
};
foo(); // foo1
var foo = function() {
console.log("foo2");
};
foo(); // foo2
然而,我们再看一段代码:
function foo() {
console.log("foo1");
}
foo(); // foo2
function foo() {
console.log("foo2");
}
foo(); // foo2
却发现打印的结果都是 foo2。这是什么原因呢?
原因是:JavaScript 引擎执行是顺序执行的,但是它并不是一行一行地分析和执行代码,而是一段一段地分析和执行的。当执行每一段代码之前,它会先做一个"准备工作"。比如第一个例子,涉及到了变量声明的提升。第二个例子,涉及到了函数声明的整体提升。
我们这节课的目的是希望大家弄清楚 JavaScript 引擎遇到一段怎样的代码时才会做"准备工作",以及这"一段一段"代码究竟是如何执行的。
可执行代码
在 JavaScript 中,可执行代码(executable code)一共分为三类:全局代码、函数代码、eval 代码。
JavaScript 执行到一个函数时,就会进行"准备工作",用一个更专业的说法就是创建"执行上下文(execution context)"。
执行上下文栈
那么问题来了,我们写的函数多了去了,JavaScript 引擎如何创建和管理这么多执行上下文呢?
答案是:JavaScript 引擎通过创建执行上下文栈(Execution context stack,ECS)来管理执行上下文。
我们通过一段代码来讲解下 JavaScript 是如何通过执行上下文栈来管理执行上下文的。
代码如下:
function fun3() {
console.log("fun3");
}
function fun2() {
fun3();
}
function fun1() {
fun2();
}
fun1();
为了模拟执行上下文栈的行为,我们定义执行上下文栈是一个数组:
ECStack = [];
当 JavaScript 开始解释执行这段代码的时候,最先遇到的就是全局代码,所以它会向执行上下文栈压入一个全局执行上下文,我们用 globalContext 表示它,并且只有当整个应用程序结束时,ECStack 才会被清空,所以程序结束之前,ECStack 最底部永远有个 globalContext:
ECStack = [globalContext];
当 JavaScript 执行一个函数时,它就会创建一个执行上下文,并且压入执行上下文栈。当函数执行完毕时,就会将函数的执行上下文从栈里弹出。知道了这个工作原理,我们再来看看对于上面这段代码,执行上下文栈是如何处理的。
// 伪代码
// fun1()
ECStack.push(<fun1> functionContext);
// fun1 中调用了 fun2,需要创建 fun2 的执行上下文,并压入执行上下文栈。
ECStack.push(<fun2> functionContext);
// fun2 还调用了 fun3,同理,创建 fun3 的执行上下文,并压入执行上下文栈。
ECStack.push(<fun3> functionContext);
// fun3 执行完毕,fun3 执行上下文弹出。
ECStack.pop();
// fun2 执行完毕,fun2 执行上下文弹出。
ECStack.pop();
// fun1 执行完毕,fun1 执行上下文弹出。
ECStack.pop();
// 如果下面还有代码,JavaScript 会接着执行下面的代码,此时 ECStack 底层还有个globalContext。
解答上一节课的思考题
在上一节深入 JavaScript 词法作用域的课中我们留下了一道思考题,让大家思考一下下面这两段代码在执行过程中有什么不同?
var scope = "global scope";
function checkscope() {
var scope = "local scope";
function f() {
return scope;
}
return f();
}
checkscope();
var scope = "global scope";
function checkscope() {
var scope = "local scope";
function f() {
return scope;
}
return f;
}
checkscope()();
首先,这两段代码返回的结果都是"local scope",不同之处在于它们的执行上下文栈的变化是不一样的。
第一段代码:
ECStack.push(globalContext);
ECStack.push(<checkscope> functionContext);
ECStack.push(<f> functionContext);
ECStack.pop();// <f> functionContext
ECStack.pop();// <checkscope> functionContext
第二段代码:
ECStack.push(globalContext);
ECStack.push(<checkscope> functionContext);
ECStack.pop();// <checkscope> functionContext
ECStack.push(<f> functionContext);
ECStack.pop();// <f> functionContext
当然了,本节仅仅是讲解了执行上下文栈的变化,并没有详细讲解执行上下文到底包含了哪些内容,这部分我会在下一节介绍。
欢迎关注微信公众号:前端阅读室