JS的代码会按照自上到下的顺序解析执行,
例如:
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,原因就是在第一个代码示例中,是变量声明提升,也就是说foo提升了; 而在第二个代码示例中,是函数声明提升,也就是说在第二个示例中第二个函数的foo覆盖了第一个foo。
执行上下文
说到执行上下文就要说到JavaScript的可执行代码;
js的可执行代码;可执行代码有三种,分别是:全局代码、函数代码、eval代码。eval现在在规范中已经不再使用,所以不在讨论之中。
比如说:当执行到一个函数的时候,就会进行准备工作,这里的‘准备工作’用更专业的说法来说就是‘执行上下文’。
函数的执行上下文栈
在一个代码中函数会很多,那么如何来管理这么多的执行上下文呢?JavaScript引擎创建了执行上下文栈来管理代码中的执行上下文。
1.首先,栈是一种先进后出的数据结构;我们可以用一个数组来模拟调用栈。
ECStack=[]
2.当遇到全局代码时,执行上下文栈会压入一个全局上下文,我们使用globalContext来表示。
ECStack.push(globalContext)
3.只有当整个程序运行结束,执行上下文栈才会被清空,所以程序结束之前,在ECStack中始终有globalContext。
现在,JS引擎遇到函数代码块了;
function fun3() {
console.log('fun3')
}
function fun2() {
fun3();
}
function fun1() {
fun2();
}
fun1();
ESCStack在fun1函数调用时会做以下事情
//每一个函数执行时都会创建一个执行上下文并被压入执行上下文栈中
//fun1执行了,创建一个context
// 压栈
ECStack.push(<fun1 context>) //发现内部还有`fun2`调用
ECStack.push(<fun2 context>) //发现内部还有`fun3`调用
ECStack.push(<fun3 context>) //发现内部还有log函数调用
ECStack.push(<log context>) //里面没了
打印fun3 //代码执行完了,该弹栈了
ECStack.pop(<log context>)
ECStack.pop(<fun3 context>)
ECStack.pop(<fun2 context>)
ECStack.pop(<fun1 context>)
此时ECStack还剩下[globalContext]
// 继续处理其他代码
// globalContext在程序结束前一直会存在
小练习
var scope = "global scope";
function checkscope(){
var scope = "local scope";
function f(){
return scope;
}
return f;
}
checkscope()();
//遇到全局上下文
ECStack.push(globalContext) //此时的调用栈[globalContext]
//调用checkscope,进入checkscope的函数内
ECStack.push(<checkscope context>) //压栈
//内部没有函数调用,返回函数f
ECStack.pop(<checkscope context>) //弹栈
//返回的f被调用了
ECStack.push(<f context>) //压栈
ECStack.pop(<f context>) //弹栈