切换主题
变量对象
在学习变量对象前,先让我们了解一下什么是执行上下文(Execution context
,简称EC
)?
执行上下文
我们知道JavaScript引擎在一段一段地分析和执行可执行代码(全局代码
、函数代码
、eval代码
)时,遇到函数执行的时候,会做一些准备工作
,这个准备工作
就是创建一个执行上下文。 由于函数不会只是一个,故JavaScript引擎为了方便管理执行上下文,采用了栈结构,对其进行管理,即执行上下文栈(Execution Context Stack
,简称ECS
)。
每个执行上下文,都包含三个重要属性:
- 变量对象(
Variable Object
,通常又称VO
): - 作用域链(
Scope Chain
); - this
变量对象
什么是变量对象呢?有何作用?
变量对象是与执行上下文相关的数据作用域,存储了在上下文中定义的变量和函数声明。
变量对象通常可分为三类:
- 全局上下文的变量对象,像
window
、global
;全局VO
是作用域链的头(终点)。
- 函数上下文的变量对象;
- 在函数上下文中,活动对象(
Activation Object
,AO
)来表示变量对象; AO
是在进入函数上下文时刻(即分析
与执行
中的分析阶段
)被创建的,它通过函数的arguments
属性初始化。
- 在函数上下文中,活动对象(
- 块级(条件判断中的
let
、const
)上下文的变量对象。
执行上下文的代码会被分成分析
和执行
两个阶段进行处理。
分析阶段
分析阶段即进入执行上下文(此时还未执行代码),此阶段会初始化变量对象,其包括:
- 函数的所有形参 (如果是函数上下文)
- 由名称和对应值组成的一个变量对象的属性被创建;
- 没有实参,属性值设为
undefined
。
- 函数声明
- 由名称和对应值(函数对象(
function-object
))组成一个变量对象的属性被创建; - 如果变量对象已经存在相同名称的属性,则完全替换这个属性。
- 由名称和对应值(函数对象(
- 变量声明
- 由名称和对应值(
undefined
)组成一个变量对象的属性被创建; - 如果变量名称跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性。
- 由名称和对应值(
这里有个非常非常非常重要的概念:Hosting
(就是函数声明提升
和变量声明提升
)
首先解释下这两概念是什么意思:
- 函数提升: 通过
function
声明的函数,JavaScript引擎在解析代码时,将其提升至其对应作用域的最顶端,若该函数名与变量名相同,则变量名会被该函数覆盖(重复声明的相同函数名,以最靠后的声明为准,同赋值操作)。 - 变量声明提升: 通过
var关键字
声明的变量,JavaScript引擎在解析代码时,会将该变量声明提升至其对应作用域的最顶端,若该变量名跟已经声明的形式参数名或函数名相同,则该变量名会被忽略。
特别注意:对于通过
var关键字
声明的变量,提升的只是声明,赋值操作不会被提升!!!
来看个简单的例子:
JavaScript
kon() // ?
var kon = function () {
console.log('yui')
}
kon() // ?
function kon() {
console.log('mio')
}
kon() // ?
高亮处的代码依次输出什么呢?mio
、yui
、yui
为什么会如此呢?因为函数提升
和变量声明提升
。
上例中的代码完全等价于下例代码:
JavaScript
var kon // 被 行2 函数声明覆盖
function kon() {
console.log('mio')
}
kon()
kon = function () { // 注意这里的赋值操作
console.log('yui')
}
kon()
kon()
回到分析阶段,看个例子(假设第二个参数为可选参数):
JavaScript
function yui(name, age) {
function sleep() { }
let fav = '吉太'
var sing = function () { }
fav = 'azusa'
}
yui('yui') // 实参只传了一个
此阶段被初始化的活动对象AO
:
JavaScript
AO = {
arguments: {
0: 'yui',
1: undefined,
length: 2
},
name: 'yui',
age: undefined,
sleep: ref to function sleep() { },
sing: ref to FunctionExpression 'sing', // 注意变量提升
fav: undefined
}
执行阶段
在代码执行阶段,会顺序执行代码,并根据代码修改活动对象AO
的值,此时的AO
:
JavaScript
AO = {
arguments: {
0: 'yui',
1: undefined,
length: 2
},
name: 'yui',
age: undefined,
sleep: ref to function sleep() { },
sing: ref to FunctionExpression 'sing', // 注意变量提升
fav: 'azusa'
}
简洁的总结我们上述:
- 全局上下文的变量对象初始化是全局对象;
- 函数上下文的变量对象初始化只包括
Arguments
对象; - 在进入执行上下文时会给变量对象添加形参、函数声明、变量声明等初始的属性值;
- 在代码执行阶段,会再次修改变量对象的属性值。