# 四.变量对象
前言
- 变量对象会保存变量声明(var)、函数参数(arguments)、函数定义(function)
- 变量对象会首先获得函数的参数变量和值
- 获取所有
function
进行函数声明,函数名为变量对象的属性名,值为函数对象,如果属性已经存在,值会用新值覆盖 - 再依次所有的 var 关键字进行的变量声明,每找到一个变量声明,就会在变量对象上建一个属性,值为
undefined
,如果变量名已经存在,则会跳过,并不会修改原属性值,let
声明的变量并不会在此阶段进行处理 - 函数声明优先级更高,同名的函数会覆盖函数和变量,但同名
var
变量并不会覆盖函数,执行阶段重新赋值可以改变原有的值
函数在运行的瞬间,生成一个活动对象(Active Object),简称 AO
- 分析参数
- 函数接收形式参数,添加到 AO 的属性,并且这个时候值为 undefined,即 AO.[xxx]=undefined
- 接收实参,添加到 AO 的属性,覆盖之前的 undefine
- 分析变量声明
- 如果上一步分析参数中 AO 还没有
xxx
属性,则添加 AO 属性为 undefined,即 AO.[xxx]=undefined - 如果 AO 上面已经有
xxx
属性了,则不作任何修改
- 如果上一步分析参数中 AO 还没有
- 分析函数声明
- 如果
function [xxx](){}
把函数赋值给 AO.[xxx],覆盖上一步分析的值
- 如果
# 1.全局执行上下文中
- 全局执行上下文中只有一个,在客户端中一般由浏览器创建,也就是我们熟知的 window 对象,我们能通过 this 直接访问到它
- window 对象也是 var 声明的全局变量的载体。我们通过 var 创建的全局对象,都可以通过 window 直接访问
# 1.1 var
var 只提前声明,在代码执行的过程中才定义赋值
- 代码
console.log(a)
var a = 1
1
2
2
- 执行过程
// 创建阶段
var globalEC = {
VO: {
a: undefined,
},
}
var ECStack = [globalEC]
// 执行阶段
console.log(globalEC.VO.a)
globalEC.VO.a = 1
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# 1.2 function
function 声明和定义都完成了
- 代码
var a = 1
function fn(m) {
console.log("fn")
}
function fn(m) {
console.log("new_fn")
}
function a() {
console.log("fn_a")
}
console.log(a)
fn(1)
var fn = "var_fn"
console.log(fn)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
- 执行过程
// 创建阶段
var globalEC = {
VO: {
a: function a() {
console.log("fn_a")
},
fn: function fn(m) {
console.log("new_fn")
},
},
}
var ECStack = [globalEC]
// 执行阶段
globalEC.VO.a = 1
console.log(globalEC.VO.a)
globalEC.VO.fn(1)
globalEC.VO.fn = "var_fn"
console.log(globalEC.VO.fn)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- 函数体中 return 后面的返回值不预解释,但是 return 后面的代码,虽然不执行,但是要预解释
function fn() {
console.log(num)
return function () {}
var num = 12
}
fn()
// - 预解释
// - function fn(){"console.log(num);return function(){};var num=12"}
// - 代码执行
// - fn()
// - 私有作用域下的预解释
// - function(){""}
// - num = undefined
// - 私有作用域下代码的执行
// - console.log(num)//undefined
// - num= 12
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 全局上下文的变量对象
- 在浏览器里,全局对象为
window
- 全局上下文的变量对象为
window
,而且这个变量对象不能激活变成活动对象 - 只要窗口打开,全局上下文会一直存在,所有的上下文都可以直接访问全局上下文变量对象上的属性
- 只有全局上下文的变量对象允许通过 VO 的属性名称来间接访问,在函数上下文中是不能直接访问 VO 对象的
- 未进入执行阶段前,变量对象中的属性都不能访问!但是进入到执行阶段后,变量对象转变成了活动对象,里面的属性都能被访问了,对于函数上下文来讲,活动对象与变量对象其实都是同一个对象,只是处于执行上下文的不同生命周期
# 1.3 语句块
不管条件是否成立,都会进行预解释
if (!("a" in window)) {
var a = 1
}
console.log(a)
// - 预解释
// - a=undefined//window.a=undefined(全局作用域下定义的)
// - 代码执行
// - !('a' in window)//false
// - console.log(a)
// 3.2 只对"="左边的进行预解释,右边的是值,不进行预解释
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
fn()
var fn = function () {
console.log(1)
}
// - 预解释
// - fn = undefined
// - 代码执行
// - fn()//报错
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
# 1.4 自执行函数
自执行函数的定义和执行是一起完成的。但是在执行的过程中,它的私有作用域中是要进行预解释的
;(function (num) {
console.log(num)
})(100)
// - 预解释
// - 无
// - 代码执行
// - console.log(100)
1
2
3
4
5
6
7
2
3
4
5
6
7
# 1.5 变量重名
- 重命名变量 a 又被声明了一次,浏览器在预解释的时候发现已经有一个同名变量 a 了,就不去重复声明了,并且在声明的时候也没有被赋值,则变量 a 还是保持原来的值不变。
- 如果预解释的时候发现重名了,不重新的声明,但是需要重新的定义。(即后面发现是重名的变量,如果该重名变量只是声明没有定义时,可以忽略,但是该重名变量声明并赋值,是需要重新声明的。)
function a(x) {}
var a
console.log(a)
// - 预解释
// - function a(x){""}
// - 代码执行
// - console.log(a)
1
2
3
4
5
6
7
2
3
4
5
6
7
# 2.函数执行上下文中
- 在 JS 执行过程会产生多个执行上下文,JS 引擎会有栈来管理这些执行上下文
- 执行上下文栈也叫做调用栈,执行栈用于存储代码执行期间创建的所有上下文,具有 LIFO(先进后出)的特性
- 栈底永远是全局上下文,栈顶为当前正在执行的上下文
- 当开启一个函数执行时会生成一个新的执行上下文并放入调用栈,执行完毕后自动出栈。
function one() {
var a = 1
debugger
function two() {
var b = 1
debugger
function three() {
var c = 1
}
three()
debugger
}
two()
debugger
}
one()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var globalExecuteContext = {
VO: {
setTimeout: "setTimeout",
},
}
var executeContextStack = [globalExecuteContext]
var oneExecuteContext = {
VO: { a: 1 },
}
executeContextStack.push(oneExecuteContext)
var towExecuteContext = {
VO: { b: 2 },
}
executeContextStack.push(twoExecuteContext)
var threeExecuteContext = {
VO: { c: 3 },
}
executeContextStack.push(threeExecuteContext)
console.log(executeContextStack)
executeContextStack.pop()
executeContextStack.pop()
executeContextStack.pop()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 2.1 激活对象
- 在函数的调用栈中,如果当前执行上下文处于函数调用栈的顶端,则意味着当前上下文处于激活状态,此时变量对象称为活动对象(AO)
- 活动变量包含变量对象所有的属性,并有包含
this
指针
function one(m) {
function two() {
console.log("two")
}
}
one(1)
1
2
3
4
5
6
2
3
4
5
6
VO 和 AO
// 执行阶段 VO=>AO
let VO = (AO = {
m: 1,
two: () => {
console.log("two")
},
})
let oneEC = {
VO,
this: window,
scopeChain: [VO, globalVO],
}
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
# 3.Eval 函数执行上下文中
- 预解释(变量提升),是指在当前作用域下,JS 代码从上到下执行之前,浏览器会默认先把带 var 和 function 关键字的进行提前声明或者定义。
- 声明:只声明,没有定义,如 var num,此时 num 的默认值是 undefined
- 定义:即赋值操作