执行上下文(栈)context(context stack)
每一次代码执行和函数调用都会产生一个执行环境,称为执行上下文。
一个执行上下文(caller)又可以激活(调用)另一个执行上下文(callee),这时caller会暂停自身的执行把控制权交给callee进入callee的执行上下文,callee执行完毕后将控制权交回caller,callee可以用return或者抛出Exception来结束自己的执行。
多个执行上下文会形成执行上下文栈,最顶层是当前执行上下文,底层是全局执行上下文。
作用域(链)scope(scope chain)
作用域是每一个执行上下文自身持有的活动对象的集合,如在本执行上下文中声明的变量和函数以及方法参数传入的对象。
每一个执行上下文可以访问的对象包括自身的作用域和父执行上下文的作用域和父父执行上下文作用域直到全局作用域,这就产生了作用域链。
作用域链的工作原理跟原型链十分相似:如果本身的作用域中查找不到标识符,那么就查找父作用域,直到顶层。
目前假设作用域的联动是用的__parent__对象,它指向作用域链的下一个对象。(在ES5中,确实有一个outer链接)
全局上下文的作用域包含Object.prototype中的对象,with和catch会改变作用域链:
Object.prototype.x = 10;
var w = 20;
var y = 30;
// in SpiderMonkey global object
// i.e. variable object of the global
// context inherits from "Object.prototype",
// so we may refer "not defined global
// variable x", which is found in
// the prototype chain
console.log(x); // 10
(function foo() {
// "foo" local variables
var w = 40;
var x = 100;
// "x" is found in the
// "Object.prototype", because
// {z: 50} inherits from it
with ({z: 50}) {
console.log(w, x, y , z); // 40, 10, 30, 50
}
// after "with" object is removed
// from the scope chain, "x" is
// again found in the AO of "foo" context;
// variable "w" is also local
console.log(x, w); // 100, 40
// and that's how we may refer
// shadowed global "w" variable in
// the browser host environment
console.log(window.w); // 20
})();
在with中,查询__parent__之前会先去查询__proto__,会使作用域链增大。
只要所有外部函数的变量对象都存在,那么从内部函数引用外部数据则没有特别之处——我们只要遍历作用域链表,查找所需变量。
注意:通过构造函数创建的方法的作用域链中只有全局变量!
var x = 10;
function foo() {
var y = 20;
function barFD() {
alert(x);
alert(y);
}
var barFE = function () {
alert(x);
alert(y);
};
var barFn = Function('alert(x); alert(y);');
barFD(); // 10, 20
barFE(); // 10, 20
barFn(); // 10, "y" is not defined
}
foo();
二维作用域链查找
源于ECMAScript的原型特性。如果一个属性在对象中没有直接找到,查询将在原型链中继续。即常说的二维链查找。(1)作用域链环节;(2)每个作用域链--深入到原型链环节:
function foo() {
alert(x);
}
Object.prototype.x = 10;
foo(); // 10
然而,如上文所提及,当一个上下文终止之后,其状态与自身将会被 销毁(destroyed) ,同时内部函数将会从外部函数中返回。
此外,这个返回的函数之后可能会在其他的上下文中被激活,那么如果一个之前被终止的含有一些自由变量的上下文又被激活将会怎样?通常来说,解决这个问题的概念在ECMAScript中与作用域链直接相关,被称为 (词法)闭包((lexical) closure)。
引出的闭包将在下章讨论。