前言
js的很多不太好理解的概念,比如作用域、this、闭包,可以说都与执行上下文有关,弄懂了执行上下文,再去理解这些概念就没有难度了。
可执行代码
但js每执行一段可执行代码,就会进入一个执行上下文;js的可执行代码有3种:
- 全局代码
- 函数代码
- eval代码
因此,js对应有3种执行上下文:全局上下文、函数上下文、eval上下文。
执行上下文栈
当js执行一段代码时,可能出现很多执行上下文,那如何管理这些上下文呢?答案是通过栈来管理,把这种用来管理执行上下文的栈称为执行上下文栈,又称调用栈。
每进入一个执行上下文,js就会向栈中push当前上下文,每退出一个执行上下文就会pop当前此上下文。因此堆栈底部永远都是全局上下文(global context),而顶部就是当前(活动的)执行上下文。
通过浏览器控制台查看调用栈
- 查看Call Stack
打开控制台,在“Source”标签下打断点,可以在右边的Call Stack中查看到调用栈的当前状态以及变化。
从上图可以看出,当执行到foo函数时,调用栈中从上到下有3个执行上下问,分别为当前上下文foo,bar上下文和全局上下文(anonymous译匿名者)。
可以通过在不同执行上下文打断点,观察Call Stack变化,可以更好的理解执行上下文栈。
- 打印调用栈
console.trace可以打印出调用栈的信息。
在foo函数内部执行console.trace(),可以打印出调用栈的当前状态。
思考题
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()();
以上2段代码的执行结果一样,但它们的调用栈变化却不一样。可以先自己分析下,然后再通过Call Stack检验下看对不对。下面做简要分析:
第一段代码,调用栈的变化过程:
Stack.push(<anonymous>)
Stack.push(<checkscope>);
Stack.push(<f>);
Stack.pop(); // <f>
Stack.pop(); // <checkscope>
Stack.pop(); // <anonymous>
第二段代码,调用栈的变化过程:
Stack.push(<anonymous>)
Stack.push(<checkscope>);
Stack.pop(); // <checkscope>
Stack.push(<f>);
Stack.pop(); // <f>
Stack.pop(); // <anonymous>
小结
通过Call Stack可以很清楚看出调用栈的变化,多试多观察栈的变化,对调用栈就会很清楚啦。
本文可以算是从宏观上讲了执行上下栈的变化,那执行上下文里面是怎样的呢?又是怎样变化的呢?
接下来讲讲与执行上下文相关的变量对象、作用域链和this。
参考:
冴羽深入js系列
汤姆大叔深入js系列
极客时间——浏览器工作原理与实践课程