在如下代码中,大家都会知道两个a打印出来分别是undefined
以及1
,那么为什么变量会提升吗?技术小白将从以下几个方面阐述,如有不对,请指正。
js的执行栈
执行上下文
上下文创建期,与执行期
console.log(a)
var a = 1
console.log(a)
js的执行栈
执行栈用于存储代码执行期间创建的所有执行上下文。具有FILO接口,也被称为调用栈。 js是单线程大家是众所周知的,每个函数,全局代码块以及eval方法在进入执行栈中运行的时候,都会先创建一个执行上下文。并将其加入到栈顶,在其运行完毕之后,弹出栈。举个例子:
function b() {
console.log('我是b')
}
function c() {
b()
console.log('我是c')
}
c()
上面代码会依次输出:
我是b, 我是c
他的运行逻辑如下(我们用数组暂且表示栈):
首先js引擎在运行期间会创建一个全局执行上下文,此时的执行栈为
[global]
在上面代码中,首先调用了c
函数,那么函数c
入栈,并创建函数c的执行上下文,此时的执行栈为
[global, 函数c]
在c函数中,调用了b函数,那么函数b入栈,并创建函数b的执行上下文,此时执行栈为
[global, 函数c,函数b]
在b函数中,调用了console.log函数,console.log函数入栈,此时执行栈为
[global, 函数c,函数b,函数cosole.log]
在console.log函数执行完之后,就会出栈,此时栈为
[global, 函数c,函数b,]
重复以上步骤,就会将执行栈清空(除全局上下文外,全局上下文始终处于栈底)
执行上下文
上文说道,执行栈用于存储代码执行期间创建的所有执行上下文,那么执行上下文是什么?有几种分类?
执行上下文是什么?
当函数执行时,会创建一个称为执行上下文的内部对象。一个执行上下文定义了一个函数执行时的环境。这个环境里面包含了他内部声明的变量,对象或其他属性等等。
执行上下文分为:
- 全局执行上下文
- 函数上下文
- eval上下文(很少用)
上下文创建期,与执行期
执行上下文分为创建期与执行期,创建期主要所做的事情包括如下(我们以函数执行上下文为例):
- 创建变量对象(variable object)vo
- 变量对象存储的是:函数的所有形参, 函数声明,以及变量声明
- 在函数还未执行前,只会对形式参数进行声明并赋值,但是对于变量来说,只会声明不会赋值。
- 在函数进入执行期的时候,变量对象被激活,跃升为活跃对象AO,只有被激活的变量对象才可以进行访问,变量对象是不可访问的。
function test(a) {
var b = 1;
var fun = val => val
}
test('stra')
在创建期间的VO为(伪代码):
VO = {
arguments: [
0: 'stra',
length: 1
],
a: 'stra', // 形参初始化并赋值
b: undefined, // 变量声明只会初始化,不会赋值
c: function
}
在执行期间,顺序执行代码,VO对象被激活,跃升为活跃对象AO,并对相应的变量进行赋值。
AO = {
arguments: [
0: 'stra',
length: 1
],
a: 'stra',
b: 1,
c: function
}
- 绑定作用域链 scope chain
- 确定this
从以上分析就可以得出结论,函数在进入执行栈的时候,会生成相应的函数执行上下文,函数执行上下文分为创建阶段以及执行阶段,在创建阶段只会对形参arguments进行声明以及赋值,对于变量只会声明,不会赋值,变量的赋值要等到执行上下文进入到执行阶段,才会进行相应的赋值,这也就是为什么会存在变量提升,因为在创建阶段就已经对变量进行了声明,只是未赋值罢了。当进入执行阶段的时候,顺序执行代码的过程中,对变量进行赋值。