深入js——执行上下文栈

前言

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(),可以打印出调用栈的当前状态。
    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系列
极客时间——浏览器工作原理与实践课程

posted @ 2020-01-17 10:55  小丸子的城堡  阅读(432)  评论(0编辑  收藏  举报