JavaScript 内功心法——执行上下文

前端必备内功之执行上下文

Posted by Shiyanping on November 8, 2018

下面的结论,主要都是因为一道面试题引发的思考,这道题的出处在这里一道js面试题引发的思考,有兴趣的可以详细看一下这个作者写的文章,我只是粗略总结。

要想深入了解执行上下文,首先要了解一下执行上下文的类型:

  • 全局执行上下文: 这是默认的、最基础的执行上下文。不在任何函数中的代码都位于全局执行上下文中。它做了两件事:1. 创建一个全局对象,在浏览器中这个全局对象就是 window 对象。2. 将 this 指针指向这个全局对象。一个程序中只能存在一个全局执行上下文。

  • 函数执行上下文: 每次调用函数时,都会为该函数创建一个新的执行上下文。每个函数都拥有自己的执行上下文,但是只有在函数被调用的时候才会被创建。一个程序中可以存在任意数量的函数执行上下文。每当一个新的执行上下文被创建,它都会按照特定的顺序执行一系列步骤,具体过程将在本文后面讨论。

  • Eval 函数执行上下文: 运行在 eval 函数中的代码也获得了自己的执行上下文,但由于 Javascript 开发人员不常用 eval 函数,所以在这里不再讨论。

大家都知道,js 在执行的时候都是按照顺序执行的,有的时候可能会受到变量提升和函数提升的影响,但是总体 js 还是保持一个执行上下文栈。

如果没有执行上下文栈,函数多了,js 引擎都忙不过来,所以 js 引擎会创建一个执行上下文栈去管理执行上下文。

我们假设将执行上下文栈定义成一个数组:

1
ECStack = [];

将全局执行上下文表示成 globalContext,在执行上下文栈工作的过程中,js 引擎会优先将全局执行上下文 push 到栈中。

1
ECStack = [globalContext];

只有当整个应用程序结束之后,ECStack 才会被清空,否则全局执行上下文将一直保持到栈中。

在执行一个函数时,创建一个执行上下文,压入到执行上下文栈中,函数执行完毕,将对应的函数执行上下文栈弹出。

拿我们之前静态作用域的例子了解一下执行上下文栈的工作原理:

1
2
3
4
5
6
7
8
9
var scope = 'global scope';
function checkscope() {
  var scope = 'local scope';
  function f() {
    return scope;
  }
  return f();
}
checkscope();
1
2
3
4
5
6
7
8
// 伪代码

ECStack.push(<checkscope> functionContext);
ECStack.push(<f> functionContext);
// f 执行完毕
ECStack.pop();
// checkscope 执行完毕
ECStack.pop();

另外一个例子:

1
2
3
4
5
6
7
8
9
var scope = 'global scope';
function checkscope() {
  var scope = 'local scope';
  function f() {
    return scope;
  }
  return f;
}
checkscope()();
1
2
3
4
5
6
7
8
// 伪代码

ECStack.push(<checkscope> functionContext);
// checkscope 执行完毕
ECStack.pop();
ECStack.push(<f> functionContext);
// f 执行完毕
ECStack.pop();

虽然我们这两个例子输出都是一样的,但是他们执行上下文是不一样的。