【我眼中的】 - 【9】变量提升

在JS中,不可避免的要声明变量和函数,那么在声明之后,JS引擎是如何找到这些变量的呢?

JS代码在执行过程中必然会进入到执行上下文中。当一个函数被调用时,一个新的上下文就会被创建。
通常一个执行上下文的生命周期分为如下阶段:

  • 创建阶段:在这个阶段中,执行上下文会创建变量对象,确定this指向,以及其他需要的状态。
  • 代码执行阶段:创建阶段完成之后,就会执行代码,完成变量的赋值,以及执行其他代码。
  • 销毁阶段:可执行代码执行完毕后,执行上下文出栈,对应的内存失去引用,等待垃圾回收器的回收。

而对于变量提升的考察,会体现在创建阶段和代码执行阶段。而在变量提升中有两个重要的概念变量对象(Variable Object,即VO)活动对象(Active Object,即AO)

变量对象(VO)

变量对象的创建,依次经历以下几个过程:①建立arguments对象 ➡️ ②检查当前上下文的函数声明 ➡️ ③检查var变量声明,创建属性

①建立arguments对象
检查当前上下文中的参数,建立该对象下的属性与属性值。

②检查当前上下文的函数声明
也就是使用function关键字声明的函数。在变量对象中以函数名建立一个属性,属性值为指向该函数所在内存地址的引用

③检查var变量声明,创建属性
检查当前上下文中的变量声明,每找到一个变量声明,就在变量对象中以变量名建立一个属性,属性值为undefined,const/let声明的变量没有赋值不能提前使用
【如果var变量与函数名同名,则在这个阶段,以函数值为准,在下一个阶段,函数值会被变量值覆盖】

从上面的例子中可以看出,function声明会比var声明优先级更高。


结合例子进行进一步讨论:

1
2
3
4
5
6
7
8
9
10
11
function test() {
console.log(a);
console.log(foo());

var a = 1;
function foo() {
return 2;
}
}

test();

对于上述例子,直接从test()的执行上下文开始理解。全局作用域中运行test()时,test()的执行上下文开始创建。创建过程可以通过如下代码展示:

1
2
3
4
5
6
7
8
9
10
11
12
// 创建过程
testEC = {
// 变量对象
VO:{},
scopeChain:{}
}

VO = {
arguments:{...},//注:在浏览器的展示中,函数的参数可能并不是放在arguments对象中,这里为了方便理解,我做了这样的处理
foo:<foo reference>, //表示foo的地址引用
a:undefined
}

在未进入执行阶段之前,变量对象的属性都不能访问!但是进入执行阶段之后,变量对象转变为了活动对象,里面的属性都能被访问了,然后开始进行执行阶段的操作。

活动对象(AO)

其实变量对象(VO)和活动对象(AO)都是同一个对象,只是处于执行上下文的不同生命周期。只有处于函数调用栈栈顶的执行对象的上下文中的变量对象才会变成活动对象。

1
2
3
4
5
6
7
8
// 执行阶段
VO -> AO // Active Object
AO = {
arguments: {...},
foo: <foo reference>,
a: 1,
this: Window
}

所以上面例子的执行顺序就变成

1
2
3
4
5
6
7
8
9
10
11
function test() {
function foo() {
return 2;
}
var a;
console.log(a);
console.log(foo());
a = 1;
}

test();

🌰 例子:

执行结果:

文章作者: qinwei
文章链接: https://qw-null.github.io/2022/06/16/9-我眼中的-变量提升/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 QW's Blog