1.什么是执行上下文?
当前JavaScript代码被解析和执行时所在环境的抽象概念。JS中运行的任何代码都是在执行上下文中运行的。
2.执行上下文的类型有3种,分别为:
(1)全局执行上下文:这是最基础的默认的执行上下文。不在任何函数中的代码都位于全局执行上下文中。它做了两件事情:
[1].创建了一个全局对象,在浏览器中的全局对象为window对象。
[2].将this指针指向这个全局对象。
注意:一个程序只能存在一个全局执行上下文。
(2)函数执行上下文:每次调用函数时,都会为该函数创建一个新的执行上下文。每个函数都有自己的执行上下文,但是只有在函数被调用时才会被创建。一个程序中可以存在任意数量的函数执行上下文。每当一个新的执行上下文被创建,他都会按照特定的顺序执行一系列操作。
(3)eval函数执行上下文:运行在eval函数中的代码也有自己的执行上下文。
3.执行上下文的生命周期(创建阶段->执行阶段->回收阶段)
当调用一个函数时,一个新的执行上下文就会被创建。而一个执行上下文的生命周期可以分为三个阶段。
(1)创建阶段(代码执行阶段前进行的):在这个阶段,执行上下文会分别创建对象、建立作用域、确定this的指向。
(2)执行阶段:创建完成之后,就会开始执行代码。这个时候,会完成变量赋值,函数引用,以及执行其他代码。
(3)回收阶段:执行完毕后出栈,等待回收。
4.变量对象
(1)变量对象的创建过程:
[1].建立arguments对象。检查当前上下文的参数,建立该对象的属性与属性值(函数的形参)
[2].检查当前上下文的函数声明,也就是试用function关键字声明的函数。在变量对象中以函数名建立一个属性,属性值为指向该函数所在内存地址的引用。如果函数名的属性已经存在那么该属性就会被新的引用所覆盖。(函数声明)
[3].检查当前上下文的变量声明,没找到一个变量声明,就在变量对象中以变量名建立一个属性,属性值为undefined,如果该变量名的属性已经存在,为了防止同名的函数被修改变为undefined,则会跳过,原属性不会改变(变量)
[4].通过例子来了解一下哈:
例子1:
function foo(){
console,log("function foo");
}
var foo = 20;
console.log(foo); //20
总结一下:变量对象的优先级为:形参>函数>变量,但是变量可以重新赋值
以上代码:变量foo的声明之前函数foo已经声明了,所以会跳过,但是在赋值的时候,变量会重新赋值。
例子2:
console.log(foo); //f foo(){console.log("function foo")
function foo(){
console.log("function foo");
}
var foo = 20;
(2)变量对象与活动对象:
[1]变量对象与活动对象其实都是同一个对象,只是处于执行上下文的不同生命周期,不过只有处于函数调用栈栈顶的执行上下文的变量对象,才会变成活动对象。
[2]例子理解:
function test(){
console.log(a);
console.log(foo());
var a = 1;
function foo(){
return 2;
}
}
test();
以上代码,在全局作用域中运行test(),test()的执行上下文开始创建,用如下形式表示:
//创建过程
testEC = {
VO:{
arguments:{...},
foo:<foo reference>, //表示foo的地址引用
a:undefined,
this:window
}
}
注意:
<1>VO为Variable Object的缩写,即变量对象。
<2>在浏览器的展示中:函数的参数可能并不是全部放在arguments对象中
[3]未进入执行阶段,变量对象的属性都不能访问,但是进入执行阶段之后,变量对象转为了活动对象,里面的属性都能被访问了,然后开始进行执行阶段的操作。
//执行阶段
VO->AO
AO = {
arguments:{...},
foo:<foo reference>,
a:1,
this:window
}
上面的正确顺序为:
function test(){
function foo(){
return 2;
}
var a;
console.log(a); //undefined
console.log(foo()); //2
a = 1;
}
test();
(3)全局上下文的变量对象
[1].全局上下文的变量对象就是window 对象,this也指向window。
[2].全局上下文的生命周期与程序的生命周期一致,只要程序运行不结束,全局上下文就一直存在。其他所有的上下文环境都能直接访问全局上下文的属性。
5.作用域链
(1).本质上是一个指向当前环境与上层环境的一系列变量对象的指针列表,作用域链保证了当前执行环境对符合访问权限的变量和函数的有序访问。
用例子理解一下:
var a = 1;
function out(){
var b = 2;
function inner(){
var c = 3;
console.log(a+b+c);
}
inner();
}
out();
总结:
[1]首先代码一开始就创建了全局上下文环境。
[2]运行out()时创建out函数的执行上下文环境。
[3]运行inner()时创建inner函数的执行上下文环境。
(2).全局作用域链:
它只包含全局作用域,没有上级,因此它的作用域链只指向本身的全局变量对象。查找标识符只能从本身的全局变量对象中找。
//全局上下文环境
globalEC = {
VO:{
out:<out reference>, //表示out的引用地址
a:undefined,
},
ScopeChain:[VO(global())], //作用域链
}
(3).函数out的作用域链:
可以引用函数out本身的变量对象以及全局的变量对象。查找标识符时,首先在函数out变量对象中寻找,找不到的话就去上一级全局变量对象寻找。
//out函数的执行上下文
outEC = {
VO:{
arguments:{...},
inner:<inner reference>, //表示inner的地址引用
b:undefined
}
ScopeChain:[VO(out),VO(global)], //作用域链
}
(4).函数inner的作用域链
可以引用函数inner本身的变量对象和上一级out函数的变量对象以及全局的变量对象。查找标识符依次从inner、out、全局变量对象中查找。
//inner函数的执行上下文
innerEC = {
VO:{
arguments:{...},
c:undefined
},
ScopeChain:[VO(inner),VO(out),VO(global)], //作用域链
}
6.执行上下文栈
(1).执行上下文可以理解为当前代码的运行环境,JS的运行环境包括3种情况:
[1]全局环境:JS代码运行起来,首先会进去该环境。
[2]函数环境:当函数被调用执行时,会进入当前函数中执行代码
[3]eval
(2).通过例子了解代码的执行过程:
var a = 1; //<1>首先进入全局上下文环境
function out (){
var b = 2;
function inner (){
var c = 3;
console.log(a+b+c);
}
inner(); //<3>进入inner函数上下文环境
}
out(); //<2>进入out函数上下文环境
执行过程如下:
[1]当前代码开始执行时,就创建全局执行上下文环境,全局上下文入栈。
[2]全局上下文入栈后,全局上下文中的代码开始执行,进行赋值,函数调用等操作,执行到out()时,激活函数out创建自己的执行上下文环境,out函数入栈。
[3]out函数上下文入栈后,其中的代码开始执行,进行赋值,函数调用等操作,执行到inner()时,激活函数inner创建自己的执行上下文环境,inner函数入栈。
[4]inner函数上下文入栈后,其中的代码开始执行,进行赋值,函数调用,打印等操作,由于里面没有可以生成其他上下文的需求,所有代码执行完毕后,inner函数上下文出栈。
[5]inner函数上下文出栈,又回到了out函数执行上下文环境,接着执行out函数中后面的代码,由于后面没有可以生成其他执行上下文的需要,所有代码执行完毕后,out函数上下文出栈。
[6]out函数上下文出栈后,又回到了全局执行上下文环境,直到浏览器窗口关闭,全局上下文出栈。
7.总结:
(1)全局上下文在代码开始执行时就创建了,只有唯一的一个,永远在栈底,浏览器窗口关闭时出栈。
(2)函数被调用时创建上下文环境。
(3)只有栈顶的上下 处于活动状态,执行其中的代码。