前言
只有理解了执行上下文与作用域链,才能更好地理解JavaScript语言的执行机制,在开发中避免不必要的错误。
一、执行上下文
简单引入:执行上下文(也称上下文)是当前代码的执行环境。(建议阅读时自动将执行上下文理解成执行环境)。
变量或函数的上下文决定了他们可以访问哪些数据,以及他们的行为。
1.类型
全局执行上下文:最外围的执行环境,在浏览器情况下,在该环境的下执行的代码会执行以下步骤:(1)创建一个window对象;(2)将this指向这个window对象;通过var定义的全局变量和函数都会成为window对象的属性和方法。
函数执行上下文:每个函数都有自己的上下文,当代码执行到该函数时,函数的上下文被推到一个上下文栈上。在函数执行完后,上下文栈会弹出该函数的上下文,将控制权返还给之前的执行上下文。
2.生命周期
每个执行上下文的生命周期都经历了:创建 -> 执行 -> 回收 三个阶段
创建阶段:
2.1.创建变量对象
引入:每个上下文都有一个关联的变量对象,变量对象是与执行上下文相关的数据作用域,用于存储在该执行上下文中的变量和函数声明。。
全局执行上下文的变量对象:全局上下文的执行环境中,变量对象就是全局对象,在浏览器中是window对象
函数执行上下文的变量对象:函数上下文执行环境中有该函数自身的变量对象,只存储了属于他自己的arguments对象,当函数进入执行阶段,其变量对象变成活动对象,此时函数上下文中声明的变量就可以被访问到,所以我们一般将活动对象称为函数执行上下文的变量对象
举个栗子:
// 全局上下文创建变量color
var color = 'blue';
// 全局上下文创建函数sayColor()
function sayColor(a, b) {
var c = 10;
var d = function() {}
function e() {}
}
console.log(window.color); // blue
console.log(window.sayColor); // f sayColor() {}
// 执行函数sayColor()
sayColor(20.30);
分析:
由内:当函数sayColor()未进入执行阶段时,它上下文创建的变量对象大致如下:
AO = {
arguments: {
0: 20,
1: 30,
length: 2
}
c: undefined,
d: undefined,
e: <function reference to e>
}
当函数sayColor()进入执行阶段时,其变量对象转变为活动对象,此时函数上下文创建的变量就可以被访问到,此时活动对象大致如下:
AO = {
arguments: {
0: 20,
1: 30,
length: 2
}
c: 10,
d: reference to Function expression to d,
e: <function reference to e>
}
到外:当函数创建完自身的变量对象后,也就是它有了自身的数据作用域,我们将目光转向更外层的执行环境,可以发现函数sayColor()在全局上下文中执行,此时同理,全局上下文创建变量对象window,存储全局上下文的变量和函数声明,大致如下:
window: {
color: 'blue',
sayColor: <function reference to sayColor>
// ...
// ...
}
2.2.this绑定(见:函数内部:this详解(重点)_JV_32的博客-CSDN博客)
- 全局执行上下文中,this值为全局对象(浏览器中,this值为window)
- 函数执行上下文中,this值取决于该函数如何被调用的
执行阶段:
2.3.创建作用域链
- 我们了解了变量对象后,代码在其上下文的执行环境的执行的过程中,会创建变量对象的一个作用域链,这个作用域链决定了各级上下文中的代码在访问变量和函数时的顺序。
- 作用域链中的下一个变量对象来自包含该变量对象更外层的变量对象
- 简单来说:沿着作用域链,内层代码可以访问外层声明变量与函数,外层无法访问内层声明的变量与函数
举同一个栗子:
// 全局上下文创建变量color
var color = 'blue';
// 全局上下文创建函数sayColor()
function sayColor(a, b) {
var c = 10;
var d = function() {}
function e() {}
}
console.log(window.color); // blue
console.log(window.sayColor); // f sayColor() {}
// 执行函数sayColor()
sayColor(20.30);
分析:此时各级上下文的变量对象关系如下:
// 全局上下文的变量对象
window: {
color: 'blue',
// 函数上下文的变量对象
sayColor = {
arguments: {
0: 20,
1: 30,
length: 2
},
c: 10,
d: reference to Function expression to d,
e: <function reference to e>
}
// ...
// ...
}
举个栗子:
var color = 'blue';
function ChangeColor() {
let anotherColor = 'red';
function swapColor() {
let tempColor = anotherColor;
anotherColor = color;
color = tempColor;
// 这里可以访问到color、anotherColor、tempColor
}
// 这里可以访问到color、anotherColor,但无法访问tempColor
swapColor();
}
// 这里只能访问color
ChangeColor();
分析:以上代码涉及三个上下文:全局上下文、changeColor()的局部上下文和swapColor()的局部上下文。内部上下文可以通过作用域链访问外部上下文中的一切,但外部上下文无法访问内部上下文中任何东西,此时各级上下文的变量对象关系如下:
// 全局上下文变量对象
window: {
color: 'blue',
// 局部函数上下文变量对象
ChangeColor = {
arguments: {
length: 0
},
anotherColor: 'red',
// 局部函数上下文变量对象的变量对象
swapColor: {
arguments: {
length: 0
},
tempColor: anotherColor
}
}
}
从变量对象分析:swapColor()的局部上下文首先从自己的变量对象开始搜索变量和函数,搜不到就去搜索上一级changeColor()变量对象的变量和函数,依次向上逐级搜索,因此可以在swapColor()中访问元素anotherColor和color。
从作用域链分析:changeColor()上下文的作用域链中包含两个对象,它自己的变量对象和全局变量对象,因此它不能访问swapColor()的上下文;同理,swapColor()上下文的作用域链中包含三个对象,他自己的变量对象、changeColor()变量对象、全局变量对象,因此它可以访问changeColor()上下文以及全局上下文。
总结
本章节通过深入浅出了解了JavaScript执行上下文的概念并引入了作用域链,为JavaScript代码的执行机制有了一定的了解,为后续了解作用域、闭包等奠定了基础。。。