1.执行上下文的概念
当函数在执行的前一刻,会创建一个称为执行期上下文的内部对象,一个执行期上下文定义了一个函数执行时的环境,函数每次执行时对应的执行上下文都是独一无二的,所以多次调用一个函数会导致创建多个执行上下文,当函数执行完毕时,执行上下文就会被销毁。
每个执行上下文的基本组成如下图所示:
2.执行期上下文的生命周期
一个执行期上下文的生命周期可以分为两个阶段:
- 创建阶段:在这个阶段中,执行上下文会分别创建变量对象,建立作用域链,确定this指向。
执行阶段:当开始执行代码的时候,会在这个阶段完成变量的赋值,函数的引用,以及执行其他的代码。
详细了解执行上下文极为重要,因为其中涉及到了变量对象,作用域链,this
等极为重要的概念,它关系到我们能不能真正理解JavaScript
,下面我们分别了解几个概念。
(一)变量对象
1.变量对象的创建过程
(1)建立arguments
对象。检查当前上下文中的参数,建立该对象下的属性与属性值。
(2)检查当前上下文的函数声明,也就是使用function
关键字声明的函数。在变量对象中以函数名建立一个属性,属性值为指向该函数所在内存地址的引用。如果函数名的属性已经存在,那么该属性将会被新的引用所覆盖。
(3)检查当前上下文中的变量声明,每找到一个变量声明,就在变量对象中以变量名建立一个属性,属性值为undefined
,如果变量名的属性已经存在,为了防止同名的函数被修改为undefined
,则会直接跳过,原属性值不会被修改。
接下来一起来看一个案例:
function bar(){
console.log('function')
}
var bar='why';
console.log(bar);//why
//在这个案例中,当变量bar遇到函数声明的bar时会直接跳过变量bar,不会将函数bar覆盖
//但是我们看到最后的输出结果为变量bar的值
//这是因为上面的三条规则只适用于变量对象的创建过程,而bar的值是在代码执行的时候被修改的
再来看另一个案例:
console.log(foo);//[Function: foo] node执行结果
function foo(){
console.log('function');
}
var foo='foo';
console.log(foo);//foo
//本例的执行顺序为:
//首先将所有的函数声明放入变量对象中
//其次将所有的变量声明放入变量对象中
//但是因为foo已经存在同名函数,所以会跳过foo的赋值
//在代码执行的阶段将foo的值赋值给变量foo,所以最后的打印结果为foo
2.变量对象与活动对象
处于函数调用栈栈顶的执行上下文中的变量对象,即为活动对象。
来看另外一个案例:
function test(){
console.log(a);//undefined
console.log(foo());//2
var a=1;
function foo(){
return 2
}
}
test();
↑以上代码中,全局作用域中运行test()时,test()的执行上下文开始创建。我们用如下的形式来表示:
//创建过程
testEC={
//VO为Variables Object的缩写,即变量对象
VO:{
arguments:{...},
foo:0xa00,//指向foo函数的内存地址
a:undefined,
this:Window
},
scopeChain:{}
}
未进入执行阶段之前,变量对象中的属性都不能访问!但是进入执行阶段之后,变量对象转变为了活动对象,里面的属性都能被访问了,然后开始进行执行的操作。
// 执行阶段
VO->AO//Active Object
AO={
arguments:{...},
foo:0xa00,
a:1,
this:Window
}
因此,上面例子的执行顺序如下:
function test(){
function foo(){
return 2
}
var a;
console.log(a);//undefined
console.log(foo());//2
a=1
}
test();
3.全局上下文的变量对象
全局上下文就是最外层的上下文,在浏览器环境中,全局上下文指window
对象,因此在浏览器环境中,通过在全局使用var
定义的变量和'function'声明的函数都会成为window对象的属性和方法,上下文在其所有的代码执行完毕后会被销毁,包括定义在它上面的变量和函数等(对于浏览器来说,关闭网页或者退出浏览器都会销毁全局上下文)
3.执行上下文栈
执行上下文可以理解为当前代码的执行环境,javascript
中的运行环境大概包括三种情况:
- 全局环境:
javascript
代码运行起来会首先进入该环境。 - 函数环境:当函数被调用执行时,会进入当前函数中执行代码。
-
eval
在代码开始执行的时候,首先会产生一个全局执行上下文环境,调用函数时,会产生函数执行上下文环境,函数调用完成后,它的执行上下文环境以及其中的数据都会被销毁,重新回到全局执行环境,网页关闭后全局执行环境也会被销毁,其实这是压栈出栈的过程,全局执行上下文环境永远在栈底,而当前正在执行的函数上下文在栈顶。
var a=1;//1.进入全局执行上下文环境
function outter(){
var b=2;
function inner(){
var c=3;
console.log(a+b+c);
}
inner();//3.进入inner函数上下文环境
}
outter();//2.进入outter函数上下文环境
↑以上代码的执行会经历以下过程:
1.当代码开始执行时就创建全局执行上下文环境,全局上下文入栈。
2.全局上下文入栈后,其中的代码开始执行,进行赋值,函数调用等操作,执行到outter()
时,激活函数outter
,函数outter
创建自己的执行上下文环境,outter
函数上下文入栈。
3.outter
函数上下文入栈后,其中的代码开始执行,进行赋值,函数调用等操作,执行到inner()
函数时,激活函数inner
创建自己的执行上下文环境,inner
函数上下文入栈。
4.inner
函数上下文入栈后,其中的代码开始执行,进行赋值,函数调用,打印等操作,由于里面没有可以生成其他上下文的需求,所有代码执行完毕后,inner
函数上下文出栈。
5.inner
函数上下文出栈后,又回到outter
函数执行上下文栈,接着执行outter
函数中后面剩下的代码,由于后面没有可以生成其它执行上下文的需求,所有代码执行完毕后,outter
函数上下文出栈。
6.outter
函数上下文出栈后,又回到了全局执行上下文环境,直到浏览器窗口关闭,全局上下文出栈。
最后我们可以得到一些结论:
- 全局上下文在代码开始执行时就创建,只有唯一的一个,永远在栈底,浏览器窗口关闭时出栈。
- 函数被调用的时候创建自己的上下文环境。
- 只有栈顶的上下文处于活动状态,执行其中的代码。