看题说话,分析一下以下代码的作用域
var x = 10;
function a() {
console.log(x);
}
function b () {
var x = 5;
a();
}
b();
简单分析一下这个程序吧,虽然不敢保证理解完全正确.先简单介绍点概念.
执行上下文
每当控制器到达ECMAScript可执行代码的时候,控制器就进入了一个执行上下文.
执行上下文是个抽象概念,标准中没有从技术实现上定义执行上下文的具体结构和类型.
就是一系列活动的执行上下文从逻辑上形成一个栈(比较抽象).
栈底总是全局上下文,栈顶是当前(活动的)执行上下文.
当在不同的执行上下文间切换(退出而进入新的执行上下文)的时候,栈会被修改(通过压栈或者出栈的形式).
变量对象
执行上下文的数据是以变量对象的属性形式进行存储的.
一个变量对象(简写为VO)是一个和执行上下文相关的特别对象,存储以下内容:
变量(声明的变量,var)
函数声明(简写为FD)
在上下文中,函数声明的形式参数
作用域链
作用域链是一条变量对象的链,它和执行上下文有关,用于在处理标识符的时候进行变量查询.
函数上下文的作用域链在函数调用的时候创建出来,它包含了活跃对象和该函数的内部[[Scope]]属性.
执行上下文变量大致如下:
activeExecutionContext = {
VO:{...},//或者AO
this:thisValue,
Scope:[
//作用域链,所有变量对象的列表,用来查询标识符
]
}
上面Scope可以定义如下:
Scope = AO+[[Scope]]
可以用数组进行表示:
var Scope = [VO1,VO2,...,VOn];//作用域链
函数的创建
在进入上下文阶段,函数声明会存储在变量/活跃对象中(VO/AO)
[[Scope]]是一个包含了所有上层变量对象的分层链,它属于当前函数的上下文,并在函数创建的时候,保存在函数中.
[[Scope]]是在函数创建的时候i保存起来的----静态的(不变的),只有一次并且一直都存在--直到函数销毁. [[Scope]]与Scope(作用域链)是不同的,前者是函数的属性,后者是上下文的属性.拿上面的来说a函数的 [[Scope]]:
a.[[Scope]] = [
globalContext.VO //===Global
]
函数调用后,就会进入函数上下文,此时创建活跃对象并且确定this的值和Scope(作用域链).
函数激活
在进入上下文,AO/VO创建之后,上下文的Scope属性(作用域链)会定义如下所示:
Scope = AO+[[Scope]]
AO会添加在作用域链的最前面
Scope = [AO].concat([[Scope]]);
处理标识符其实就是一个确定变量(或者函数声明)属于作用域中哪个变量对象的过程.
此算法返回的总是一个引用类型的值,其base属性就是对应的变量对象(或者如果变量不存在的时候返回null),其property name属性的名字就是要查询的标识符.
标识符处理过程包括了对应的变量名的属性查询,如:在作用域链中会进行一系列的变量对象的检测,从作用域链的最底层上下文一直到最上层上下文
所以,在查询过程中上下文中的局部变量相比较上层上下文的变量会优先被查询到
分析如下:
全局上下文的变量对象如下:
globalContext.VO===Global={
x:10,
a:,
b:
}
在"a"函数创建的时候,其[[Scope]]属性如下所示:
a.[[Scope]]=[
globalContext.VO
]
在"b"函数创建的时候,其[[Scope]]属性如下所示:
b.[[Scope]]=[
globalContext.VO
]
在"b"函数激活的时候(进入上下文时),"b"函数上下文的活跃对象如下:
bContext.AO={
x:5
}
同时,"b"函数上下文的作用域如下所示:
bContext.Scope=[bContext.AO,globalContext.VO]
在"a"函数激活的时候(进入上下文时),"a"函数上下文的活跃对象如下:
aContext.AO={
}
同时,"a"函数上下文的作用域如下所示:
aContext.Scope=[aContext.AO,globalContext.VO]
"x"标识符的查找过程:
"x"
----aContext.AO//没有找到
----globalContext.VO //找到 10