EC={VO:{/* 函数中的arguments对象, 参数, 内部的变量以及函数声明 */},this:{},Scope:{ /* VO以及所有父执行上下文中的VO */}}现在让我们看一个包含全局和函数上下文的代码例子:
很简单的例子,我们有一个被紫色边框圈起来的全局上下文和三个分别被绿色,蓝色和橘色框起来的不同函数上下文。只有全局上下文(的变量)能被其他任何上下文访问。
你可以有任意多个函数上下文,每次调用函数创建一个新的上下文,会创建一个私有作用域,函数内部声明的任何变量都不能在当前函数作用域外部直接访问。在上面的例子中,函数能访问当前上下文外面的变量声明,但在外部上下文不能访问内部的变量/函数声明。为什么会发生这种情况?代码到底是如何被解释的?
2、ECS—执行上下文栈
一系列活动的执行上下文从逻辑上形成一个栈。栈底总是全局上下文,栈顶是当前(活动的)执行上下文。当在不同的执行上下文间切换(退出的而进入新的执行上下文)的时候,栈会被修改(通过压栈或者退栈的形式)。
压栈:全局EC—>局部EC1—>局部EC2—>当前EC
出栈:全局EC<—局部EC1<—局部EC2<—当前EC
我们可以用数组的形式来表示环境栈:
ECS=[局部EC,全局EC];每次控制器进入一个函数(哪怕该函数被递归调用或者作为构造器),都会发生压栈的操作。过程类似javascript数组的push和pop操作。

我们已经知道,当浏览器首次载入你的脚本,它将默认进入全局执行上下文。如果,你在你的全局代码中调用一个函数,你程序的时序将进入被调用的函数,并穿件一个新的执行上下文,并将新创建的上下文压入执行栈的顶部。
如果你调用当前函数内部的其他函数,相同的事情会在此上演。代码的执行流程进入内部函数,创建一个新的执行上下文并把它压入执行栈的顶部。浏览器将总会执行栈顶的执行上下文,一旦当前上下文函数执行结束,它将被从栈顶弹出,并将上下文控制权交给当前的栈。下面的例子显示递归函数的执行栈调用过程:
(function foo(i) {if (i === 3) {return;}else {foo(++i);}}(0));
这代码调用自己三次,每次给i的值加一。每次foo函数被调用,将创建一个新的执行上下文。一旦上下文执行完毕,它将被从栈顶弹出,并将控制权返回给下面的上下文,直到只剩全局上下文能为止。
有5个需要记住的关键点,关于执行栈(调用栈):
VO: { // 上下文中的数据 ( 函数形参(function arguments), 函数声明(FD),变量声明(var))}1)、进入执行上下文时,VO的初始化过程具体如下:VO(functionContext) === AO;AO是在进入函数的执行上下文时创建的,并为该对象初始化一个arguments属性,该属性的值为Arguments对象。
AO = { arguments: {callee:,length:,properties-indexes: //函数传参参数值 }};FD的形式只能是如下这样:function f(){}当函数被调用是executionContextObj被创建,但在实际函数执行之前。这是我们上面提到的第一阶段,创建阶段。在此阶段,解释器扫描传递给函数的参数或arguments,本地函数声明和本地变量声明,并创建executionContextObj对象。扫描的结果将完成变量对象的创建。function foo(i) {var a = ‘hello‘;var b = function privateB() {};function c() {}}foo(22);当调用foo(22)时,创建状态像下面这样:fooExecutionContext = {scopeChain: { ... },variableObject: {arguments: {0: 22,length: 1},i: 22,c: pointer to function c()a: undefined,b: undefined},this: { ... }}真如你看到的,创建状态负责处理定义属性的名字,不为他们指派具体的值,以及形参/实参的处理。一旦创建阶段完成,执行流进入函数并且激活/代码执行阶段,看下函数执行完成后的样子:fooExecutionContext = {scopeChain: { ... },variableObject: {arguments: {0: 22,length: 1},i: 22,c: pointer to function c()a: ‘hello‘,b: pointer to function privateB()},this: { ... }}2、VO示例:alert(x); // functionvar x = 10;alert(x); // 10x = 20;function x() {};alert(x); // 20进入执行上下文时,ECObject={ VO:{x:<reference to FunctionDeclaration "x"> }};执行代码时:ECObject={ VO:{x:20 //与函数x同名,替换掉,先是10,后变成20 }};对于以上的过程,我们详细解释下。VO = {};VO["x"] = <引用了函数声明"x">// 发现var x = 10;// 如果函数“x”还未定义// 则 "x" 为undefined, 但是,在我们的例子中// 变量声明并不会影响同名的函数值VO["x"] = <值不受影响,仍是函数>执行代码阶段,VO被修改如下:VO["x"] = 10;VO["x"] = 20;如下例子再次看到在进入上下文阶段,变量存储在VO中(因此,尽管else的代码块永远都不会执行到,而“b”却仍然在VO中)
if (true) {var a = 1;} else {var b = 2;}alert(a); // 1alert(b); // undefined, but not "b is not defined"3、AO示例:function test(a, b) {var c = 10;function d() {}var e = function _e() {};(function x() {});}test(10); // call当进入test(10)的执行上下文时,它的AO为:testEC={AO:{arguments:{callee:testlength:1,0:10},a:10,c:undefined,d:<reference to FunctionDeclaration "d">,e:undefined}};由此可见,在建立阶段,VO除了arguments,函数的声明,以及参数被赋予了具体的属性值,其它的变量属性默认的都是undefined。函数表达式不会对VO造成影响,因此,(function x() {})并不会存在于VO中。testEC={AO:{arguments:{callee:test,length:1,0:10},a:10,c:10,d:<reference to FunctionDeclaration "d">,e:<reference to FunctionDeclaration "e">}};可见,只有在这个阶段,变量属性才会被赋具体的值。(function() {console.log(typeof foo); // 函数指针console.log(typeof bar); // undefinedvar foo = ‘hello‘,bar = function() {return ‘world‘;};function foo() {return ‘hello‘;}}());我们能回答下面的问题: