在ECMASscript中的代码有三种类型:global, function和eval(eval是通过传入字符串动态的插入代码,后面的代码并不知道eval中代码是什么)。每一种类型都有它们自己的执行上下文。只有一个全局执行上下文,可能有多个function和eval的执行上下文。一个函数可以生成无数个执行上下文,甚至递归。执行上下文,或者全局上下文调起全局的一个函数上下文,这叫执行上下文栈。调起其他上下文的上下文被称为caller。被调起的上下文被成为callee。当一个caller激活一个callee的时候,一个执行上下文就会被callee激活。callee某些时候也会是调用其他callee的caller
。当caller调起callee,caller暂停执行,将控制流传递给callee。callee被放进 执行上下文栈。
程序执行最开始是在执行上下文栈的最底部的全局执行上下文。然后全局上下文提供了一些初始化,创造所需要的对象以及函数。全局上下文执行期间,代码会激活其他已经创建的函数,进入新的执行上下文,push新的元素到栈中。初始化执行之后,运行系统会等待其他激活函数或者进入新的上下文的时间。在下图中,有类似E1的执行上下文和globalEC全局执行上下文,通过globalEC改变进入或推出EC1.
这就是ECMAscript的运行系统如何管理代码的执行。
每个执行上下文可以呈现为一个对象,我们来看一下他的结构和什么状态下执行上下文执行他的代码。
- 执行上下文
一个执行上下文可以抽象的被表示成一个对象。每个执行上下文设置的属性(我们可以叫做执行上下文的状态),需要跟踪执行上下文进程的相关代码。下面的图呈现的是执行上下文的结构:
除了这三个需要的属性(变量,this,作用域链),一个执行上下文的实现可能需要增加额外的状态。
- 变量对象
变量对象是存放这执行上下文相关数据的容器,他是存储定义在上下文中变量和函数声明的对象。
注意:变量中不包含函数表达式(对比函数声明)。
变量是抽象概念,在不同的上下文类型中,运用不同的对象。比如:在全局执行上下文中,变量对象是全局对象本身(这就是为什么我们能够通过全局对象的属性名引用全局对象)。
- 激活对象
- 作用域链
- 闭包
- this
不同execution context的變項不會互相影響─了解function背後運作的邏輯
闭包:使用静态作用域是闭包的一个强制性要求。
// global "x"
var x = 10;
// global function
function foo() {
console.log(x);
}
(function (funArg) {
// local "x"
var x = 20;
// there is no ambiguity,
// because we use global "x",
// which was statically saved in
// [[Scope]] of the "foo" function,
// but not the "x" of the caller's scope,
// which activates the "funArg"
funArg(); // 10, but not 20
})(foo); // pass "down" foo as a "funarg"
还有一个很重要的点,几个函数可能含有相同的父级作用域(这是一个很普遍的情况,例如有好几个内部或者全局的函数)。在这种情况下,在[[Scope]]中存在的变量是会共享的。一个闭包中变量的变化,也会影响另一个闭包的。
function baz() {
var x = 1;
return {
foo: function foo() { return ++x; },
bar: function bar() { return --x; }
};
}
var closures = baz();
console.log(
closures.foo(), // 2
closures.bar() // 1
);
this:
this是和执行的上下文环境息息相关的一个特殊对象。因此,它也可以称为上下文对象[context object]。this是执行上下文环境的一个属性,而不是某个变量对象的属性。
在函数上下文[function context]中,this会可能会根据每次的函数调用而成为不同的值.this会由每一次caller提供,caller是通过调用表达式[call expression]产生的(也就是这个函数如何被激活调用的)。