变量对象:
1.变量对象(variable object) 是与执行上下文相关的 数据做用域(scope of data) 。
它是与上下文关联的特殊对象,用于存储被定义在上下文中的 变量(variables) 、 函数声明(function declarations) 和 函数的形参 。
2.就像咱们所说的, VO就是执行上下文的属性(property):
activeExecutionContext = {
VO: {
// 上下文数据(var, FD, function arguments)
}
};
3.在函数执行上下文中,VO是不能直接访问的,此时由活动对象(activation object,缩写为AO)扮演VO的角色。
4.活动对象是在进入函数上下文时刻被建立的,它经过函数的arguments属性初始化。arguments属性的值是Arguments对象:
AO = {
arguments: <ArgO>
};
****5.执行上下文的代码被分红两个基本的阶段来处理:
- 进入执行上下文:这个阶段AO/VO已经拥有了属性,不过并非全部的属性都有值,大部分属性的值仍是系统默认的初始值undefined
- 执行代码:这个阶段AO/VO属性值被修改
6.一般,各种文章和JavaScript相关的书籍都声称:“无论是使用var关键字(在全局上下文)仍是不使用var关键字(在任何地方),均可以声明一个变量”。请记住,这是错误的概念:
任什么时候候,变量只能经过使用var关键字才能声明。
7.填充VO的优先级顺序是: 函数的形参 -> 函数申明 -> 变量申明。
做用域链:
1.做用域链正是内部上下文全部变量对象(包括父变量对象)的列表。此链用来变量查询。
var x = 10;
function foo() {
var y = 20;
function bar() {
alert(x + y);
}
return bar;
}
foo()(); // 30
上面的例子中,“bar”上下文的做用域链包括AO(bar)、AO(foo)和VO(global)。
2.函数上下文的做用域链在函数调用时建立的,包含活动对象和这个函数内部的[[scope]]属性。下面咱们将更详细的讨论一个函数的[[scope]]属性。
在上下文中示意以下:
activeExecutionContext = {
VO: {...}, // or AO
this: thisValue,
Scope: [ // Scope chain
// 全部变量对象的列表
// for identifiers lookup
]
};
其scope定义以下:
Scope = AO + [[Scope]]
<上面代码的意思是:活动对象是做用域数组的第一个对象,即添加到做用域链的前端。>
3.[[scope]]特色:
•[[scope]]是全部父变量对象的层级链,处于当前函数上下文之上,在函数建立时存于其中。
•注意这重要的一点--[[scope]]在函数建立时被存储--静态(不变的),永远永远,直至函数销毁。即:函数能够永不调用,但[[scope]]属性已经写入,并存储在函数对象中。
•另一个须要考虑的是--与做用域链对比,[[scope]]是函数的一个属性而不是上下文。
4.若是一个属性在对象中没有直接找到,查询将在原型链中继续。即常说的二维链查找。
活动对象没有原型,咱们能够在下面的例子中看到:
function foo() {
var x = 20;
function bar() {
alert(x);
}
bar();
}
Object.prototype.x = 10;
foo(); // 20
3、闭包:
1.闭包是代码块和建立该代码块的上下文中数据的结合。
闭包是一系列代码块(在ECMAScript中是函数),而且静态保存全部父级的做用域。
闭包就是可以读取上层上下文内部变量的函数。
2.在ECMAScript中,全部的函数都是闭包,由于它们都是在建立的时候就保存了上层上下文的做用域链(除开异常的状况) (无论这个函数后续是否会激活 —— [[Scope]]在函数建立的时候就有了)
3.在ECMAScript中,同一个父上下文中建立的闭包是共用一个[[Scope]]属性的。也就是说,某个闭包对其中[[Scope]]的变量作修改会影响到其余闭包对其变量的读取
这就是说:全部的内部函数都共享同一个父做用域
var data = [];
for (var k = 0; k < 3; k++) {
data[k] = function () {
alert(k);
};
}
data[0](); // 3, 而不是0
data[1](); // 3, 而不是1
data[2](); // 3, 而不是2
上述例子就证实了 —— 同一个上下文中建立的闭包是共用一个[[Scope]]属性的。所以上层上下文中的变量“k”是能够很容易就被改变的。
activeContext.Scope = [
... // 其它变量对象
{data: [...], k: 3} // 活动对象
];
data[0].[[Scope]] === Scope;
data[1].[[Scope]] === Scope;
data[2].[[Scope]] === Scope;
这样一来,在函数激活的时候,最终使用到的k就已经变成了3了。以下所示,建立一个闭包就能够解决这个问题了:
var data = [];
for (var k = 0; k < 3; k++) {
data[k] = (function _helper(x) {
return function () {
alert(x);
};
})(k); // 传入"k"值
}
// 如今结果是正确的了
data[0](); // 0
data[1](); // 1
data[2](); // 2
函数“_helper”建立出来以后,经过传入参数“k”激活。其返回值也是个函数,该函数保存在对应的数组元素中。这种技术产生了以下效果: 在函数激活时,每次“_helper”都会建立一个新的变量对象,其中含有参数“x”,“x”的值就是传递进来的“k”的值。这样一来,返回的函数的[[Scope]]就成了以下所示:
data[0].[[Scope]] === [
... // 其它变量对象
父级上下文中的活动对象AO: {data: [...], k: 3},
_helper上下文中的活动对象AO: {x: 0}
];
data[1].[[Scope]] === [
... // 其它变量对象
父级上下文中的活动对象AO: {data: [...], k: 3},
_helper上下文中的活动对象AO: {x: 1}
];
data[2].[[Scope]] === [
... // 其它变量对象
父级上下文中的活动对象AO: {data: [...], k: 3},
_helper上下文中的活动对象AO: {x: 2}
];
咱们看到,这时函数的[[Scope]]属性就有了真正想要的值了,为了达到这样的目的,咱们不得不在[[Scope]]中建立额外的变量对象。