# 顺序执行
如果要问到 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()