前几个礼拜没有好好的写文章,其实也有在写,很多都是草稿,因为,写着写着就发现,一些点还是不够通透,于是就又去开始学习了,循序渐进也挺好。经过了几个周的反思、积累和沉淀,emmm…是时候总结出自己脑子里的东西了,今天讲讲变量对象和执行期上下文,
执行期上下文,这是一个比较不容易描述的点,但是又真实存在,对于执行期上下文,网上也有很多的理解,有通俗的认为执行期上下文就是一段可执行的代码段,这是从表层去理解,的确,执行期上下文是建立在一块可执行代码段的基础之上的,但是仅理解到此是不够的。
个人的理解:执行期上下文是一个环境,它建立在一段可执行代码的基础上,并且只针对该段代码块,一般是函数内部,函数被创建时随之而生,它包含了变量对象,作用域链,以及this,函数执行时,就是在这一段执行期上下文中进行操作,而定义函数时呈现的代码部分是给人看的,执行期上下文是系统内部看的,也就是说,系统不会看代码写成什么样,只会看函数所对应的执行期上下文是什么样。
变量对象(Variable Object)是一个比较底层的概念,因为它的存在只在函数执行的前一刻产生,存在于执行期上下文中,其内部包含了定义的属性,变量和方法。以window为例。window属于全局对象,其内部包含了已经定义的各种属性以及函数,例如Math、Date、toString()…等等,总之很多,他们可以通过this引用。除此之外,window还作为全局变量的宿主存在,作为window的属性,指向变量自身。例如我们在全局中定义一个a变量,既可以通过this直接访问,也可以通过window.a进行访问。说这么多其实就是以window打个样,window对象就相当于一个变量对象,只是它是全局的执行期上下文中的变量对象。
为了区分全局上下文中的变量对象,与函数上下文中的变量对象,将全局的变量对象称为VO(Variable Object),而将函数上下文中的变量对象称为AO(Active Object),也叫活动对象。其实都是变量对象VO
好了,是时候展示真正装逼了…
有必要说说函数执行阶段,真正操作的是谁了.
以一段代码为例:
function foo(a){
var b = 2;
function c(){}
var d = function(){
console.log('d');
}
b = 3;
}
foo(1);
foo函数是一段可执行的代码段,在函数执行阶段,有一样东西将被添加到ESC执行栈中,此时函数被激活,这样东西就是产生的一段关于自身的执行期上下文,暂且以fooContext为名。其内部存的变量对象VO也被激活AO,活动对象是在进入函数上下文时刻被创建的,它通过函数的 arguments 属性初始化。arguments 属性值是 Arguments 对象。AO内部保存着其内部声明的变量及方法。
函数执行前,执行期上下文初始化:
fooContext:{
AO:{
arguments:{
0:1,
length:1
},
a:1,//被arguments对象初始化了
b:undefined,
c:reference to function c(){},
d:undefined
}
}
这段执行期上下文就是函数执行前一刻所产生的。
到代码执行阶段,操作的就是这段执行期上下文,不会再去看代码是怎么写的了!但是你也许会问,这玩意是怎么生成的啊,万一有变量名字一样的怎么办,并且万一变量名和函数名也一样怎么办,问的好!!!
送你三个锦囊妙计,用以生成执行期上下文之需:
- 首先获取函数的所有形参,以及被在函数内被var关键字声明的变量,将它们当作AO的属性挂载到AO对象上,值为undefined。
- 形参实参相统一,就是通过arguemnts对象初始化之前挂载的变量,有值的赋值,没值为undefined。像之前的例子中foo(1)中的实参值为1,所以AO中的a值就为1.
- 获取所有function关键字声明的函数,并将其函数名作为属性名挂载到AO中,值就为对应的函数。如果存在有变量名相同的情况,哼哼,直接覆盖。
有此三计,保你无忧,额当然要是有些比较2的人,直接打印一个不声明的变量,唉,没办法,也说说吧。
两种情况:
还是以之前的为例;
1.直接打印未声明,未赋值的变量。
console.log(e);//直接报错e is not defined,
e = 1;//赋值也不知道给谁赋,这玩意是谁啊,不认识。而且写在这有毛线用,上一句都报错了。想赋也赋不到啊
2.直接打印未声明,但是赋值了的变量。
e = 1;//虽然未声明,但是会被隐式的当作window对象的属性
console.log(e);//这里涉及到作用域,自己没有,可以向外部作用域查找,window有,所以打印1.
好了,接着之前的代码,说说函数执行阶段:
开始赋值:
b = 2;
d = function(){ console.log('d');},
b = 3;//之前的2被覆盖
执行结束后的执行其上下文:
fooContext:{
AO:{
arguments:{
0:1,
length:1
},
a:1,
b:3,
c:reference to function c(){},
d:reference to FunctionExpression:d
}
}
我们甚至可以观察函数执行的每一步的输出结果:
function foo(a){
console.log(b);
var b = 2;
console.log(b);
function c(){}
console.log(c);
console.log(d);
var d = function(){
console.log('d');
}
console.log(d);
b = 3;
console.log(b);
}
foo(1);
分析分析:
- 从第一个console开始看,第一个打印undefined。函数开始执行阶段,此时执行期上下文中AO的b为undefined,所以读取到的b也就为undefined。
- 第二个打印2,函数执行了b=2,为b赋了新值2,此时执行期上下文中AO的b就变为2,所以从上下文中读取到的b也就为2.
- 第三个打印c的函数体,因为执行期上下文中AO的c此时的值就是函数体本身。
- 第四个打印为undefined,在这里要说明一点,用var声明和用function声明是不同的,function声明在上下文中的效果会直接将函数体作为属性值,直接赋值,而以var声明的,会被当作变量,变量只有在函数执行时,才会进行赋值操作。此处就是以var声明的,虽然值为函数,但依然被当作以变量声明的方式执行。
- 第五个打印d等号后的函数体,也就是变量赋值。不要问我为什么不打印‘d’,因为函数没执行。
- 第六个打印3,b被赋予新值,AO中的b的值也就改变。所以打印3。
最后再附上一幅图,以便于理解。
对于变量对象和执行期上下文就说到此处。
对于执行期上下文还涉及到一个作用域链的生成问题。开篇时提到了在执行期上下文中存在三个重要的点,变量对象,this,作用域链。打算下一篇就说明此处。