变量提升的原因

668 阅读4分钟

在如下代码中,大家都会知道两个a打印出来分别是undefined以及1,那么为什么变量会提升吗?技术小白将从以下几个方面阐述,如有不对,请指正。

  1. js的执行栈
  2. 执行上下文
  3. 上下文创建期,与执行期
   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,]

重复以上步骤,就会将执行栈清空(除全局上下文外,全局上下文始终处于栈底)

执行上下文

上文说道,执行栈用于存储代码执行期间创建的所有执行上下文,那么执行上下文是什么?有几种分类?
执行上下文是什么?
当函数执行时,会创建一个称为执行上下文的内部对象。一个执行上下文定义了一个函数执行时的环境。这个环境里面包含了他内部声明的变量,对象或其他属性等等。
执行上下文分为:

  1. 全局执行上下文
  2. 函数上下文
  3. eval上下文(很少用)

上下文创建期,与执行期

执行上下文分为创建期与执行期,创建期主要所做的事情包括如下(我们以函数执行上下文为例):

  1. 创建变量对象(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 
}
  1. 绑定作用域链 scope chain
  2. 确定this

从以上分析就可以得出结论,函数在进入执行栈的时候,会生成相应的函数执行上下文,函数执行上下文分为创建阶段以及执行阶段,在创建阶段只会对形参arguments进行声明以及赋值,对于变量只会声明,不会赋值,变量的赋值要等到执行上下文进入到执行阶段,才会进行相应的赋值,这也就是为什么会存在变量提升,因为在创建阶段就已经对变量进行了声明,只是未赋值罢了。当进入执行阶段的时候,顺序执行代码的过程中,对变量进行赋值。