深入理解函数的执行上下文栈
18888628835 opened this issue · 0 comments
18888628835 commented
代码执行顺序
JS的代码会按照顺序执行,例如
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
了,原因在于第一个代码示例中,是变量提升,也就是foo
提升了。而第二个代码示例属于函数提升,也就是第二个函数foo
覆盖了第一个foo
。这个题目也许很多面试题中都会有,在这里不过多讨论,使用这两个例子,只是为了说明,JS
的代码在运行时,JS
引擎会做一些准备工作。
那么JS
引擎遇到怎样的代码才会做这样的准备工作呢?
可执行代码
这就要说到 JavaScript 的可执行代码(executable code)的类型有哪些了。
可执行代码有三种,分别是全局代码、函数代码、eval代码
eval现在在规范中已经不再使用,所以不在讨论之内。
比如说:当执行到一个函数的时候,就会进行准备工作,这里的“准备工作”,让我们用个更专业一点的说法,就叫做"执行上下文(execution context)"。
函数的执行上下文栈
因为代码中函数很多,如何管理这么多的执行上下文呢?JavaScript 引擎创建了执行上下文栈(Execution context stack,ECS)来管理执行上下文。
既然叫栈,那么它的数据结构有点明朗了,它属于先进后出的数据结构,我们可以使用一个数组来模拟调用栈。
ECStack=[]
当遇到全局代码时,执行上下文栈会压入一个全局上下文,我们使用globalContext
来表示
ECStack.push(globalContext)
只有当整个程序运行结束,执行上下文栈才会被清空,所以程序结束之前,在ECStack
中始终有globalContext
。
现在,JS引擎遇到函数代码了
function fun3() {
console.log('fun3')
}
function fun2() {
fun3();
}
function fun1() {
fun2();
}
fun1();
ESCStack
在fun1
函数调用时会做以下事情
//每一个函数执行时都会创建一个执行上下文并被压入执行上下文栈中
//fun1执行了,创建一个context
// 压栈
ECStack.push(<fun1 context>) //发现内部还有`fun2`调用
ECStack.push(<fun2 context>) //发现内部还有`fun3`调用
ECStack.push(<fun3 context>) //发现内部还有log函数调用
ECStack.push(<log context>) //里面没了
打印fun3 //代码执行完了,该弹栈了
ECStack.pop(<log context>)
ECStack.pop(<fun3 context>)
ECStack.pop(<fun2 context>)
ECStack.pop(<fun1 context>)
此时ECStack还剩下[globalContext]
// 继续处理其他代码
// globalContext在程序结束前一直会存在
小练习
下面我们来写一下以下代码的执行上下文栈
var scope = "global scope";
function checkscope(){
var scope = "local scope";
function f(){
return scope;
}
return f;
}
checkscope()();
//遇到全局上下文
ECStack.push(globalContext) //此时的调用栈[globalContext]
//调用checkscope,进入checkscope的函数内
ECStack.push(<checkscope context>) //压栈
//内部没有函数调用,返回函数f
ECStack.pop(<checkscope context>) //弹栈
//返回的f被调用了
ECStack.push(<f context>) //压栈
ECStack.pop(<f context>) //弹栈