简单理解:执行上下文是评估和执行Javascript代码的环境的一个抽象概念。任何代码在JavaScript中运行时,都在执行上下文中运行。
以ES3举例,对于每个执行上下文,都有三个重要属性:
变量对象
作用域链
this
变量对象
每个执行上下文都有一个关联的变量对象,这个变量对象存储着其关联的执行上下文中定义的所有变量和方法。这个变量对象无法通过代码访问,但是后台处理数据会用到它。
作用:帮助js解析器找到我们平时调用的变量和方法
下面将结合代码一起解释
全局执行上下文
全局执行上下文:在浏览器中,全局执行上下文就是window对象
分析如下代码执行过程
console.log(a);
console.log(b);
var a = 10;
var b = 20;
function b() {
}
预编译阶段:
第一步:创建变量对象(vo),寻找变量声明,并将变量作为键名添加给变量对象,赋值为undefined
vo = {
a: undefined,
b: undefined
}
第二步:寻找函数声明,并将变量作为键名添加给变量对象,赋值为函数体,如果发现变量对象已经存在这个键名了,则直接覆盖掉之前的
vo = {
a: undefined,
b: function b() {
}
}
执行阶段(输出结果):
undefined
[Function: b]
函数执行上下文
在函数执行上下文中,用活动对象表示变量对象(可以理解为当进入到到一个函数执行上下文中,这个变量对象才会激活,所以叫活动对象)。
活动对象最初只有一个定义变量arguments,指代arguments对象,调用函数时所传所有参数都会传给这个类数组对象
分析如下代码执行过程
function fn(a, d) {
console.log(a)
var a = 10
console.log(a)
console.log(b)
function a() {
}
if (false) {
var c = 20
}
console.log(c)
var b = function () {
}
function d() {
}
console.log(d)
}
fn(1, 2)
预编译阶段:
第一步:创建活动对象(ao),寻找形参和变量声明,作为ao的键名,赋值为undefined
ao = {
a: undefined,
d: undefined,
c: undefined,
b: undefined
}
第二步:实参将会赋值给函数中对应的形参
ao = {
a: 1,
d: 2,
c: undefined,
b: undefined,
}
第三步:寻找函数声明,并将变量作为键名添加给变量对象,赋值为函数体,如果发现变量对象已经存在这个键名了,则直接覆盖掉之前的
ao = {
a: function a() {
},
d: function d() {
},
c: undefined,
b: undefined,
}
执行阶段(输出结果):
[Function: a]
10
undefined
undefined
[Function: d]
总结:js解析器是通过变量对象(或活动对象)来找到我们平时调用的变量和方法,但我们是无法通过代码访问变量对象的。
执行上下文栈
用来管理执行上下文的栈(简称执行栈),被用来存储代码运行时创建的所有执行上下文。
当JS初始化时,会自动将全局执行上下文压入栈中(全局执行上下文在应用程序退出前才会销毁,比如关闭浏览器)之后每当调用一个函数,就会为该函数创建一个函数执行上下文并压入栈中,JS引擎会执行那些执行上下文位于栈顶的函数。当对应函数执行完毕时,会将其函数执行上下文从栈中弹出,然后将控制权返还给当前栈中的下一个上下文。
var a = 'a'
function b() {
console.log('Function b')
c()
console.log('Again Function b')
}
function c() {
console.log('Function c')
}
b()
console.log('window')
//输出结果
Function b
Function c
Again Function b
window
当调用b函数时,创建b的函数执行上下文,并push进执行栈中,然后输出行3。当调用c函数时,创建c的函数执行上下文,并push进执行栈中,然后输出行9,函数c执行return undefined后,其上下文弹出执行栈,然后输出行5,函数b return undefined,其上下文弹出执行栈。最后输出window

作用域链
上下文代码在执行过程中,会创建变量对象的一个作用域链。这个作用域链决定了各级上下文中的代码在访问变量和函数时的顺序。
代码正在执行的上下文的变量对象始终位于作用域链前端,作用域链中的下一个变量对象来自包含上下文,再下一个变量对象来自再下一个包含上下文,以此类推至全局上下文;全局上下文的变量对象始终是作用域链的最后一个。
作用域链作用:在函数执行时求出标识符的值
var global = 'globalData'
function fn1() {
var outerPart = 'outerPartData'
function fn2() {
var innerPart = 'innerPartData'
console.log(innerPart, outerPart, global)
}
fn2()
}
fn1()
对于fn2上下文来说:

对于fn1来说:

总结:内部上下文可通过作用域链访问访问外部上下文的一切,而外部上下文无法访问内部上下文中的任何东西。
关于作用域更多可查看我的另一篇博客—Javascript作用域与闭包
this
上下文创建过程中会确定this值,this和执行上下文是绑定的,每个执行上下文中都有一个this。
全局执行上下文中,this 的值指向全局对象,在浏览器中this 的值指向 window对象
函数执行上下文中,this 的值取决于函数的调用方式
this作用:在函数体内部,指代函数当前的运行环境(因为函数可以在不同的运行环境执行,所以this作用,就是在函数体内部获得当前的运行环境)
var a = 1;
var fn = function () {
console.log(this)
console.log(this.a);
}
var obj = {
fn: fn,
a: 2,
};
fn() // window 1
obj.fn() // {a: 2, fn: ƒ} 2
以上是关于ES3执行上下文的全部内容
总结:以ES3为前提,执行上下文是比较抽象的概念,把它简单理解为当前代码的运行环境即可。它是由JS引擎创建,服务于代码执行。创建执行上下文有3步,创建变量对象 、创建作用域链、确定this指向。他们各自的用途是,帮助JS引擎查找变量或方法、给具体变量赋值、确定函数当前运行环境。
了解执行上下文机制有助于更好的让我们理解JS中的一些特点,比如变量提升、函数提升、作用域等。
ES5的执行上下文机制引入了词法环境、变量环境,虽然有所区别,但是两者的共同点也挺多,所以学习ES3的也不亏!
会继续出一篇关于this指向的,先不放链接了。