重学JavaScript(4) - 变量对象

113 阅读4分钟

一个执行上下文的生命周期有下面几个阶段:

  • 创建阶段:在执行上下文中创建变量对象、确定作用域和this指向等
  • 执行阶段:完成代码执行,其中包括变量赋值,函数引用,执行代码等
  • 销毁阶段:可执行代码执行完成后,执行上下文出栈,销毁内存空间

从上面可以看出执行上下文中有三个属性尤为重要:

  • 变量对象

  • 作用域

  • this

只有充分理解这三个属性,才能够真正理解执行上下文,以及JavaScript是怎么运行的。

变量对象

因为不同的执行上下文下的变量对象有所不同,所以分两个情况进行讨论

全局执行上下文

全局上下文比较特殊,它的变量对象就是全局对象,也就是window对象

全局对象:是预定义的对象,作为 JavaScript 的全局函数和全局属性的占位符。通过使用全局对象,可以访问所有其他所有预定义的对象、函数和属性。

  • 全局对象会预定义一系列函数和属性
console.log(Math.random())
  • 在全局执行上下文中可以通过this调用
var a = 1;
console.log(this.a); // 1
console.log(window.a); // 1
  • 其他的执行上下文都可以访问全局对象
var a = 1;
function getName(){
	var name = '旺财';
	console.log(window.a)
}
getName()

// 结果 1

函数执行上下文

为了更好得理解函数执行上下文中变量对象,将执行上下文分为三个部分。

初始化

初始化变量对象时,会在对象初始化一个arguments属性,其属性值Arguments(一个类数组对象),包括如下属性:

  • callee — 指向当前函数的引用
  • length — 真正传递的参数个数
  • properties-indexes (字符串类型的整数) 属性的值就是函数的参数值(按参数列表从左到右排列)。 properties-indexes内部元素的个数等于arguments.length. properties-indexes 的值和实际传递进来的参数之间是共享的。

执行阶段前

变量对象在进入执行阶段之前,经历一下几个过程:

  1. 初始化函数的所有形参, 属性值为undefined;
  2. 检查当前上下文的函数声明,也就是使用function关键字声明的函数。在变量对象中以函数名建立一个属性,属性值为指向该函数所在内存地址的引用。
  3. 检查当前上下文中的变量声明,每找到一个变量声明,就在变量对象中以变量名建立一个属性,属性值为undefined,const/let 声明的变量没有赋值,不能提前使用

注意:如果变量名称跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性,函数声明会比变量声明优先级更高一点。

function test(a,b){
	console.log(a);
	console.log(b);
	console.log(c);
	console.log(foo());
	
	var c = 20;
	function foo(){
		return 2;
	}
	var d = function() {};
}

test(10)

进入test函数的执行上下文创建时,会生成如下的变量对象。

// 创建阶段
VO = {
    arguments: {
        0: 10,
        1: undefined,
        length: 1
    }
    c: undefined,
    d: undefined,
    foo: <foo reference>,  // 表示foo的地址引用
}

执行阶段后

变量对象在进入执行阶段之前,经历一下几个过程:

  • 变量对象(Variable Object,VO)转变为活动对象(Active Object)
  • 变量赋值

变量对象和活动对象的区别如下:

变量对象和活动对象没有本质区别,都是同一个对象,只是出于执行上下文的不同生命周期。未进入执行阶段前,变量对象中的属性都不能访问,进入执行阶段后,变量对象转变为活动对象,里边的属性都能够被访问,然后开始执行阶段的操作。

// 执行阶段
VO ->  AO   // Active Object
AO = {
    arguments: {
        0: 10,
        1: undefined,
        length: 1
    }
    c: 20,
    d: <d reference>,
    foo: <foo reference>,  // 表示foo的地址引用
}

因此最开始的代码执行顺序应该如下:

function test(a,b){
	function foo(){
		return 2;
	}
	var c;
	var d;
	
	console.log(a);
	console.log(b);
	console.log(c);
	console.log(d);
	console.log(foo());
	
	c = 20;
	d = function() {};
}

test(10)

//输出结果
10
undefined
undefined
undefined
2

参考

  1. JavaScript深入之变量对象

  2. 前端基础进阶(三):变量对象详解