在上一篇文章中我们讨论了有关执行上下文,它是编译阶段创建的第一个执行上下文。
我们称这个第一个执行上下文为全局执行上下文(global EC),这个执行上下文存储的相关变量是全局变量。
全局执行上下文并不是唯一的,当脚本运行时,多个执行上下文可以被创建或移出。
那么这些执行上下文来自哪?
一个执行上下文与编译阶段联系在一起,那么多个执行上下文暗示有多个编译阶段。
这个机制与函数有关
上面的代码中,我们知道在程序运行结束后控制台会打印20,但是在执行上下文里到底发生了什么?
一如既往,从编译开始,首先创建一个全局执行上下文
在变量环境中,我们可以看见“apple”,“appleTotal”变量和“total”函数,编译结束,并且执行开始。
“apple”变量的值被更新为10,然后js引擎阅读“total()”部分
在此刻,同样两个步骤被重复。编译阶段开始,但是这个时候,它仅仅发生在total函数
js引擎创建一个新的执行上下文,total函数的执行上下文,并且将它堆叠在全局执行上下文的顶部,我们称这个结构为栈。
栈有一个特点:后进,先出。这也是我们在这个机制中唯一关心的特点。
与全局执行上下文相同,一个变量环境存在与total函数执行上下文中,里面一个值为undefined的“price”变量
接下来,total函数的执行上下文开始执行阶段,“price”变量的值被更新为2
然后,这个函数在total执行上下文找到“price”变量,在全局执行上下文找到“apple”变量。
很好,它找到了所有变量并计算出结果返回
在同一时刻,这个返回值在全局执行上下文中被赋值给“appleTotal”变量。
接下来total执行上下文中没有可执行的脚本了,所以js引擎将它从栈中移出。
total执行上下文是最后一个进栈的,所以它是第一个离开栈的。
现在,栈底还有最后一个执行上下文,全局执行上下文。
仅仅保留的可执行脚本是在控制台打印appleTotal的值,这个做完后,整个程序运行完成。
从这个例子中,我们可以看到js引擎如何管理执行栈。
- js引擎在一个函数被调用之前不会编译这个函数里的代码
- 当一个函数被编译,一个新的执行上下文被创建,并且被放置到栈顶
- 所有的执行上下文被管理在一个栈结构中,这个过程发生从栈顶到栈底
- 任何时候一个函数被调用,都会发生编译和执行两个步骤
根据执行上下文在栈顶相互堆叠,我们称之为调用栈。
在浏览器中检擦你的js调用栈
当代浏览器,在它的开发工具中很容易检查它的调用栈
以chrome为例
为了检查调用栈,我们需要:
- 一个HTML文件
- 一个断点
HTML文件的代码如下,当然,你也可以给出自己的代码
<script>
var apple = 10;
function total() {
var price = 2;
return apple * price;
}
var appleTotal = total();
console.log(appleTotal);
</script>
在chrome中运行这个HTMl文件,并且在开发者工具中打开source面板
在source面板上,我们在total函数里打一个断点
接下来刷新页面,调用栈将会展示出来。那个total是total函数的执行上下文,并且“anonymous”是全局执行上下文。
结论:
- 多个执行上下文是被管理在一个栈结构中,被称为js调用栈。
- js引擎重复两个步骤过程,编译和执行,为了添加和移除执行上下文。
- 新的执行上下文被添加到栈的最上面,最上面的执行上下文优先完成执行。
- 仅仅只有一个全局执行上下文,并且它总是在栈的底部,它在浏览器开发工具中展示为“anonymous”(匿名)调用栈。