正如你所见, 执行上下文可以在逻辑上构成一个堆栈. 首先, 可能有一段全局代码和其自己的执行上下文, 然后这段代码可能会调用一个函数, 并带着它(函数)的执行上下文. 这段函数可以调用另外一个函数, 等等等等. 即使函数是递归调用的, 每次调用时被也会进入一个新的执行上下文.
活动对象(Activation object) / 变量对象(Variable Object):
每一个执行上下文都有一个跟其所关联的所谓变量对象(Variable Object). 类似于执行上下文, 变量对象是一个抽象实体, 一种用来描述变量实例的机制. 有趣之处在于, 在源代码中声明的变量和函数通常会被当做属性(properties)增加到这个变量对象上.
当程序控制进入全局代码的执行上下文时, 一个全局对象(Global object)被用来作为一个变量对象. 这正是为什么声明为全局的函数变量会变成全局对象属性的原因.
复制代码 代码如下:
/* remember that `this` refers to global object when in global scope */
var GLOBAL_OBJECT = this;
var foo = 1;
GLOBAL_OBJECT.foo; // 1
foo === GLOBAL_OBJECT.foo; // true
function bar(){}
typeof GLOBAL_OBJECT.bar; // "function"
GLOBAL_OBJECT.bar === bar; // true
好, 所以全局变量会变成全局对象的属性, 但是局部变量(那些在函数代码中定义的变量)会发生什么呢? 其实它们的行为也非常类似: 它们会变成变量对象(Variable object)的属性. 唯一的不同在于, 当在函数代码中时, 一个变量对象并不是全局对象, 而是所谓的活动对象(Activation object). 活动对象在会每次进入函数代码的执行上下文时被创建.
并不是只有在函数代码中声明的变量和函数会变成活动对象的属性; 这也会在每个函数参数(对应相应的形式参数的名称)和一个特殊的Arguments对象(以arguments为名称)上发生. 注意, 活动对象是一个内部描述机制, 在程序代码中并不能被访问.
复制代码 代码如下:
(function(foo){
var bar = 2;
function baz(){}
/*
In abstract terms,
Special `arguments` object becomes a property of containing function's Activation object:
ACTIVATION_OBJECT.arguments; // Arguments object
...as well as argument `foo`:
ACTIVATION_OBJECT.foo; // 1
...as well as variable `bar`:
ACTIVATION_OBJECT.bar; // 2
...as well as function declared locally:
typeof ACTIVATION_OBJECT.baz; // "function"
*/
})(1);
最后, 在Eval代码中声明的变量会成为调用者上下文(calling context)的变量对象的属性. Eval代码只是简单地使用调用它的代码的执行上下文的变量对象.
复制代码 代码如下:
var GLOBAL_OBJECT = this;
/* `foo` is created as a property of calling context Variable object,
which in this case is a Global object */
eval('var foo = 1;');
GLOBAL_OBJECT.foo; // 1
(function(){
/* `bar` is created as a property of calling context Variable object,
which in this case is an Activation object of containing function */
eval('var bar = 1;');
/*
In abstract terms,
ACTIVATION_OBJECT.bar; // 1
*/
})();
属性的特性(property attributes)
我们几乎是已经在这了. 既然我们已经很清楚在变量上发生了什么(它们变成了属性), 唯一剩下的需要理解的概念就是属性的特性(property attributes)了. 每一个属性可以拥有0个或多个特性, 它们从以下集合中选取: ReadOnly, DontEnum, DontDelete和 Internal. 你可以把它们认为是flags —— 一种特性可以在属性中存在, 也可以不存在. 对于我们今天的讨论来说, 我们只对DontDelete感兴趣.
当被声明的变量和函数成为变量对象(或者函数代码的活动对象, 或全局代码的全局对象)的属性时, 这些属性在创建时就带上了DontDelete的特性. 然而, 任何显式(或隐式)的属性赋值所建立的属性将不会被带上DontDelete特性. 这就是为什么我们能够删除一些属性, 但删除不了其它的.
复制代码 代码如下: