我们讲到了,当一段代码被执行时,JavaScript引擎先会对其进行编译,并创建执行上下
文。但是并没有明确说明到底什么样的代码才算符合规范
什么样的代码会经历编译和执行?
1、 全局代码,当执行一段全局代码的时候会创建全局执行上下文,全局执行上下文会一直存在,存在整个页面的生命周期,有且只有一份。
2、函数 当执行函数的时候会对函数的代码进行编译,创建函数执行上下文,当函数执行结束的时候一般会回收执行上下文的资源,为啥是一般因为有特例比如闭包。
3、eval eval执行代码的时候也会创建上下文
调用栈
调用栈就是用来管理函数调用关系的一种数据结构
1、函数调用和栈结构
var test = 'admin';
function add() {
var b = 2;
return b;
}
add();
全局执行上下文 变量环境(test = undefined add -> 函数对象) 词法环境 代码
代码中全局变量和函数都保存在全局上下文的变量环境中
执行上下文准备好之后,便开始执行全局代码
当执行到add这儿时,JavaScript判断这是一个函数调用 1、首先,从全局执行上下文全局执行上下文中,取出add函数代码 2、其次,对add函数的这段代码进行编译,并创建该函数的执行上下文该函数的执行上下文和可执行代码可执行代码 3、最后,执行代码,输出结果
就这样,当执行到add函数的时候,我们就有了两个执行上下文了⸺全局执行上下文和add函数的执行上下文。
也就是说在执行JavaScript时,可能会存在多个执行上下文,那么JavaScript引擎是如何管理这些执行上下
文的呢?答案是通过一种叫栈的数据结构来管理的通过一种叫栈的数据结构来管理的。那什么是栈呢?它又是如何管理这些执行上下文呢?
什么是栈
可以理解为一个只有一头的停车场,先进去的需要等着前面的都出来他才能出来,先进后出。
JavaScript引擎正是利用栈的这种结构来管理执行上下文的。在执行上下文创建好后,JavaScript引擎会将执行上下文压入栈中,通常把这种用来管理执行上下文的栈称为执行上下文栈执行上下文栈,又称调用栈调用栈
var a = 1;
function add(b,c){
return b+c
}
function addAll(b,c){
var d = 2
result = add(b,c)
return a+result+d
}
addAll(3,4)
第一步,创建全局上下文,并将其压入栈底
变量a、函数add和addAll都保存到了全局上下文的变量环境对象中
全局执行上下文压入到调用栈后,JavaScript引擎便开始执行全局代码了。首先会执行a=2的赋值操作,执行该语句会将全局上下文变量环境中a的值设置为2。设置后的全局上下文的状态如下图所示:
第二步是调用addAll函数。当调用该函数时,JavaScript引擎会编译该函数,并为其创建一个执行
上下文,最后还将该函数的执行上下文压入栈中,如下图所示
按照上面的那个步骤继续执行
最后的全部都入栈的效果
为什么用栈结构存储调用站因为可以直接控制rsp rbp【这个是汇编的】 v8采用累加寄存器和通用寄存器配合后面会讲解 这里就理解移动指针就行了 指针直接移指针执行出栈的操作。 控制指针就可以完成栈的操作。
出栈完了最后就是这样子的。
至此,整个JavaScript流程执行结束了
现在知道如何追踪一个函数调用的机制了。
在开发中,如何利用好调用栈 鉴于调用栈的重要性和实用性,那么接下来我们就一起来看看在实际工作中,应该如何查看和利用好调用栈。
1.如何利用浏览器查看调用栈的信息1.如何利用浏览器查看调用栈的信息
通过Chrome的调试工具可以看到 从图中可以看出,右边的“callstack”下面显示出来了函数的调用关系:栈的最底部是anonymous,也就是全局的函数入口;中间是addAll函数;顶部是add函数。这就清晰地反映了函数的调用关系,所以在分析在分析复杂结构代码,或者检查Bug时,调用栈都是非常有用的复杂结构代码,或者检查Bug时,调用栈都是非常有用的。
console.trace()来输出当前的函数调用关系
栈溢出
function call(){
call(){}
}
call(); // 调用死循环 虽然没有其他变量但是栈针也需要空间所以瞬间就把栈干溢出了。
总结
每调用一个函数,JavaScript引擎会为其创建执行上下文,并把该执行上下文压入调用栈,然后
JavaScript引擎开始执行函数代码
如果在一个函数A中调用了另外一个函数B,那么JavaScript引擎会为B函数创建执行上下文,并将B函数
的执行上下文压入栈顶。
当前函数执行完毕后,JavaScript引擎会将该函数的执行上下文弹出栈
当分配的调用栈空间被占满时,会引发“堆栈溢出”问题