# 顺序执行

如果要问到 JavaScript 代码执行顺序的话,相比写过 JavaScript 的开发者都会有一个直观的印象,那就是顺序执行,毕竟:

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。

这是因为 JavaScript 引擎并非一行行分析、执行程序,而是一段段分析执行。当执行一段代码的时候,会进行一个准备工作,比如第一个例子中的变量提升和第二个例子中的函数提升。

但本文真正想让大家思考的是这一段段中的段究竟是怎样划分的,到底 JS 引擎遇到一段怎样的代码才会做准备工作。

# 可执行代码

这就要说到 JS 的可执行代码(executable code)的类型有哪些了?

  • 全局代码
  • 函数代码
  • eval 代码

举个例子,当执行到一个函数的时候,就会进行准备工作,这里的准备工作就叫做 ***执行上下文(execution context)***。

每个执行上下文都有三个重要属性:

  • 变量对象(Variable object)
  • 作用域链(Scope chain)
  • this

# 执行上下文栈

JS 引擎为了管理诸多的执行上下文,它会创建一个 ***执行上下文栈(Execution context stack, ECS)***:

var ECStack = []

最开始执行的是全局代码,所以初始化的时候首先会向执行上下文压入一个全局执行上下文(globalContext),只有当整个应用程序结束的时候,ESC 才会被清空,所以程序结束前,栈底永远有一个 globalContext。

现在 JS 遇到了下面这段代码:

function fun3() {
  console.log('fn3')
}

function fun2() {
  fun3()
}

function fun1() {
  fun2()
}

fun1()

所以引擎是这样解析代码的:

// fun1()
ECStack.push(<fun1>, functionContext)
// fun1竟然调用了fun2,还要创建fun2的执行上下文
ECStack.push(<fun2>, functionContext)
// 我去,竟然还要创建fun3的执行上下文
ECStack.push(<fun3>, functionContext)
// fun3执行完毕
ECStack.pop()
// fun2执行完毕
ECStack.pop()
// fun1执行完毕
ECStack.pop()

# 思考题

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()();

这两段代码执行结果一样,但是它们的执行上下文变化不同。

// 第一段
ECStack.push(<checkscope>, functionContext)
ECStack.push(<f>, functionContext)
ECStack.pop()
ECStack.pop()

// 第二段
ECStack.push(<checkscope>, functionContext)
ECStack.pop()
ECStack.push(<f>, functionContext)
ECStack.pop()