一:什么是执行上下文?
JS代码在执行前,JS引擎总要做一番准备工作,这份工作其实就是创建对应的执行上下文;
执行上下文有且只有三类,全局执行上下文,函数上下文,与eval上下文;由于eval一般不会使用,这里不做讨论。
当JavaScript代码运行的时候,确定它运行所在的环境是非常重要的。运行环境由下面三种不同的代码类型确定:
全局代码(Global Code):代码首次执行时候的默认环境
函数代码(Function Code):每当执行流程进入到一个函数体内部的时候
Eval代码(Eval Code):当eval函数内部的文本执行的时候
您可以在网上找到大量关于scope
的参考资料。
1.全局执行上下文
全局执行上下文只有一个,在客户端中一般由浏览器创建
,也就是我们熟知的window
对象,我们能通过this直接访问到它。
全局对象window上预定义了大量的方法和属性,我们在全局环境的任意处都能直接访问这些属性方法,同时window对象还是var声明的全局变量的载体。我们通过var创建的全局对象,都可以通过window直接访问。
image image2.函数执行上下文
函数执行上下文可存在无数个,每当一个函数被调用时都会创建一个函数上下文;需要注意的是,同一个函数被多次调用,都会创建一个新的上下文。
说到这你是否会想,上下文种类不同,而且创建的数量还这么多,它们之间的关系是怎么样的,又是谁来管理这些上下文呢,这就不得不说说执行上下文栈了。
注意!全局上下文有且只有一个,程序中其他任意的上下文都可以访问全局上下文。你可以拥有任意数量的函数上下文。每一次函数调用都会创建一个新的上下文,它会创建一个私有域,函数内部做出的所有声明都会放在这个私有域中,并且这些声明在当前函数作用域外无法直接访问。一个函数可以访问它所在的上下文外部的变量,但是一个外部的上下文无法访问内部函数内部声明的变量/函数。为什么会发生这样的情况?代码究竟是如何被解析的呢?
二:执行上下文栈
执行上下文栈(下文简称执行栈)也叫调用栈,执行栈用于存储代码执行期间创建的所有上下文,具有LIFO(Last In First Out后进先出,也就是先进后出)的特性。
JS代码首次运行,都会先创建一个全局执行上下文并压入到执行栈中,之后每当有函数被调用,都会创建一个新的函数执行上下文并压入栈内;由于执行栈LIFO的特性,所以可以理解为,JS代码执行完毕前在执行栈底部永远有个全局执行上下文。
浏览器中的JS解释器是单线程的。也就是说在浏览器中同一时间只能做一个事情,其他的action和event都会被排队放入到执行栈中(Execution Stack)。下图表示了一个单线程栈的抽象视图
image如我们所知,当一个浏览器第一次load你的代码的时候,首先它会进入到一个全局执行上下文中。如果在你的全局代码中,你调用了一个函数,那么程序的执行流程会进入到被调用的函数中,并创建一个新的执行上下文,并将这个上下文推入到执行栈顶。
如果在当前的函数中,你又调用了一个函数,那么也会执行同样的操作。执行流程计入到刚被调用的函数内部,重新创建一个新的执行上下文,并再次推入到执行栈顶。浏览器会一直执行当前栈顶的执行上下文,一旦函数执行完毕,该上下文就会被推出执行栈。下面的例子展示了一个递归函数以及该程序的执行栈:
(function foo(i) {
if (i === 3) {
return;
}
else {
foo(++i);
}
}(0));
image
这个代码循环调用了三次,每次对i累加1。每次函数foo调用的时候,都会有一个创建新的执行上下文。一旦上下文完成了执行,就会推出栈,将控制流返回给它下面的执行上下文,这样一直到全局上下文。
例2:
function f1() {
f2();
console.log(1);
};
function f2() {
f3();
console.log(2);
};
function f3() {
console.log(3);
};
f1();//3 2 1
我们通过执行栈与上下文的关系来解释上述代码的执行过程,为了方便理解,我们假象执行栈是一个数组,在代码执行初期一定会创建全局执行上下文并压入栈,因此过程大致如下:
//代码执行前创建全局执行上下文
ECStack = [globalContext];
// f1调用
ECStack.push('f1 functionContext');
// f1又调用了f2,f2执行完毕之前无法console 1
ECStack.push('f2 functionContext');
// f2又调用了f3,f3执行完毕之前无法console 2
ECStack.push('f3 functionContext');
// f3执行完毕,输出3并出栈
ECStack.pop();
// f2执行完毕,输出2并出栈
ECStack.pop();
// f1执行完毕,输出1并出栈
ECStack.pop();
// 此时执行栈中只剩下一个全局执行上下文
关于执行栈,有5点需要记住:
- 单线程
- 同步执行
- 一个全局上下文
- 无数的函数上下文
- 每次函数调用都会创建一个新的执行上下文,即使是调用自身
三:执行上下文详解
我们已经知道每当一个函数调用发生,都会创建一个新的执行上下文。但是在JS解释器内部,每次调用一个执行上下文都分为两个步骤
- 创建阶段[在函数被调用,但还未执行任何代码之前]
- 激活/代码执行阶段:
- 分配变量,以及到函数的引用,然后解析/执行代码
一个执行上下文从概念上可以视为一个包含三个property的Object
网友评论