执行上下文的生命周期分为两个阶段:创建阶段和代码执行阶段。
创建阶段:
- 生成变量对象
- 确定作用域链
- 确定this的指向
生成变量对象的过程如下:
- 建立arguments对象:检查当前上下文中的参数(函数参数),建立该对象下的属性和属性值。
- 函数中的所有形参加入到变量对象中。
函数参数名作为一个变量对象的一个属性被创建,值为对应传进来的值 ,否则为undefined.
- 当前执行上下文中的函数声明的提升,函数名作为变量对象的一个属性被添加,值为函数的引用。如果变量对象中存在一个同名函数,则会对前一个函数进行覆盖。
- 变量提升。以变量名为变量对象的一个属性被添加,值为undefined。如果变量对象中存在一个同名函数命,那么该变量名不会影响到函数名。(可以理解为:重名的变量名不会再被添加进变量对象了)
VO与AO
VO为变量对象,AO为活动对象,其实,他们是同一个对象的两种状态,就是说,在执行上下文的创建阶段会生成VO,在执行上下文的代码执行阶段,VO就变成了AO。
- 函数表达式不包含在VO中
- 没使用Var声明的变量不包含在VO中
在全局执行上下文中,变量对象就是全局对象。只有全局上下文的变量对象允许通过VO的属性名称间接访问;函数执行上下文中,VO是不能直接访问的,此时AO代替了VO,从而通过AO来访问。
有一点需要注意
不使用var声明的变量是一个全局变量,那么只有在通知了系统这是一个全局变量之后才能访问。
更具体地说,其实不使用var创建的变量,即下面的代码中的这种方式创建的,是相当于给全局对象global添加了一个属性。由于全局对象是共享的,因此只有在属性添加之后才可以访问,否则会报错。
function foo() {
console.log(a);
a = 1;
}
foo(); //Uncaught ReferenceError: a is not defined。
关于执行上下文中的非匿名立即执行函数
var foo = 1
(function foo() {
foo = 10
console.log(foo)
}())
这段代码的执行结果为:
ƒ foo() {
foo = 10
console.log(foo)
}
这个可以理解为:在()中创建了一个变量对象,里面有一个foo属性,执行一个函数,在函数内部可以访问到这个foo属性,但是这个foo是只读的,因此赋值改写被忽略,因此结果为foo这个函数。
关于let和var
JS引擎在扫描代码发现变量声明时,要么将他们提升至作用域顶部(遇到var声明),要么将他们放入TDZ(遇到let和const声明)。访问TDZ中的变量会触发运行时错误。只有执行过变量声明语句后,变量才会从TDZ中出来,然后才可以正常访问。
网友评论