深入JS之执行上下文

358 阅读4分钟

深入JS之执行上下文

什么是上下文?

上下文的定义是:上下文就是程序需要执行时所需要的环境,具体来说就是各个变量和数据,包括所有的寄存器变量、进程打开的文件、内存信息等。

从以上的概念中可以看出,上下文无非就是存储一些信息,包括变量,内存信息等。

什么是执行上下文?

当JS代码执行到一段可执行的的代码(全局代码,函数代码 eval代码)时候,就会做一系列的准备工作。这些准备工作包括以下几个方面:

  1. 创建变量对象
  2. 绑定作用域链
  3. 绑定this

以上操作即是执行上下文所做的事情,执行上下文说的通俗一点就是帮程序管理好变量与内存等。

执行上下文的工作可以分为两部分:

  1. 创建过程
  2. 执行过程

创建过程

创建过程主要所做的事情,就是申请内存地址,用来存储变量对象VO(Variable object):

  1. 对于函数声明的所有形参,会以参数名: 参数值存储到变量对象VO中去
  2. 对于函数声明,会以函数名: function-object存储到变量对象VO中去
  3. 对于普通的声明,比如:var, let等,他会先判断VO中有没有与这个变量名一样的函数声明或普通声明,如果有的话,就不做更改,如果没有的话,那么就会添加进去VO中去。不会进行初始化
  4. 绑定作用域链scope(本文不做具体展开,就是因为绑定了作用域链,所以才会有作用域的链式访问,如果一个AO中没有某个属性,就会去他的作用域上面去找。类似于原型链)

举个例子:

function test(a, d) {
    function b(){};
    var b = 1;
    var c = 1;
}
test(1, 2)

在上面的函数中,首先会将形参a 形参的a:1,d:2的形式存储到VO中的中去,并在arguments存储{ 0: 1, 1: 2 }。这也就是为啥可以通过arguments拿到所有参数的原因。

接下来遇到了函数声明b,那么会以b:function-object存储到VO中去,

接下来遇到了普通声明b,但是在VO中已经有一个函数声明为b了,按照他的规则,他不会对现有的进行更改,因此他不会被添加到VO中去,因此此时VO中的b还是一个函数声明

接下来遇到了普通声明c,在VO中没有任何一个声明是c,将c以c:undefined的形式插入到VO中,

因此最后的VO就是:

VO: {
    arguments: {
        0: 1,
        1: 2,
    },
     a: 1,
     d: 2,
     b: reference to function c(){},
}

执行阶段

在初始化之后,进入代码执行阶段的时候,就会根据顺序对函数中的变量进行修改,此时,VO就变成了活跃对象AO,只有是活跃对象才可以被访问,因此此时变成了

AO: {
    arguments: {
        0: 1,
        1: 2,
    },
     a: 1,
     d: 2,
     b: reference to function c(){},
}

在函数的前2行都是没有进行任何赋值操作的,在函数的第三行将b重新赋值成了数字类型,因此此时的AO就变成了

AO: {
    arguments: {
       0: 1,
       1: 2, 
    },
     a: 1,
     b: 1,
     d: 2,
}

当函数执行完成之后,那么这个执行上下文就会从栈中弹出,如果没有其他函数引用该AO中的变量的话,那么整个AO就会被销毁,如果有的话,则除了被引用的,其余的都销毁。

练习题

console.log(foo) // function foo(){}
function foo(){
    
}
var foo = 1;
console.log(foo) // 1
/*
	当进入执行上下文的时候,会先进入创建阶段,创建阶段的VO格式如下
	VO: {
		arguments: {
		
		},
		foo: reference to function c(){},
	},
	然后进入执行阶段,执行阶段的时候VO激活成AO,在执行第一行的console.log的时候,因为此时的AO中的foo还是一个函数声明,因此打印function foo(){}
	函数执行到var foo = 1;会对AO中的变量foo赋值,值为1,因此此时的AO为
	AO: {
		arguments: {
		
		},
		foo: 1, // var foo = 1;重新赋值
	},
	在执行最后一行的consle.log的时候,此时foo已经变成了一个数字类型,因此打印的是1
*/