执行上下文
基本概念:
**执行上下文:**一个函数运行之前,创建的一块内存空间,空间中包含有该函数执行所需要的数据,为该函数执行提供支持。这块内存空间与包含的数据就是执行上下文(可以理解为一个对象)。
**执行上下文栈:**call stack,所有执行上下文组成的内存空间。
函数执行过程: JS 引擎每次执行栈顶的执行上下文。
全局执行上下文: 可以理解为所有代码的入口函数(main)所形成的执行上下文。
函数执行与入栈过程
html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Document</title>
</head>
<body>
<script>
console.log('global 1');
function A() {
console.log('A1');
function B() {
console.log('B');
}
B();
console.log('A2');
}
A();
console.log('global 2');
</script>
</body>
</html>
输出结果:
js
global 1
A1
B
A2
global 2
执行上下文栈的变化过程:
注意点:
- 调用函数时会创建该函数的执行上下文(入栈)
- 函数调用完毕会销毁该函数的执行上下文(出栈)
- JS 引擎每次执行栈顶的执行上下文。
执行上下文包含的内容:
基础概念:
VO: variable object 中记录了该环境中所有声明的参数、变量和函数
GO: global object 全局执行上下文中的VO
AO: active object 当前正在运行的执行上下文中的VO
每次执行函数时,会创建一个新的执行上下文,执行上下文包含两部分内容,this 与 VO
this
this 的三种指向:
- 直接调用函数,this 指向全局对象
- 在函数外,this 指向全局对象
- 通过对象调用或 new 一个函数,this 指向调用的对象或新对象
vo
VO 是一个对象,记录该环境中所有声明的参数、变量和函数
函数执行过程与执行上下文栈的详细构造过程
注意点:
- 全局执行上下文在程序开始时默认初始化在栈底
- 函数调用时创建新的执行上下文
- 创建新的执行上下文时要确定执行上下文中的内容
将用以下实例代码说明执行上下文栈的变化过程:
html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Document</title>
</head>
<body>
<script>
console.log(this.name);
var a = 2;
function A() {
this.abc = 123;
function B() {
console.log(this);
}
B();
}
var a = new A();
console.log(a.abc);
</script>
</body>
</html>
第一步:初始化执行上下文栈与全局执行上下文,并确定全局执行上下文中的 this 与 VO 的值

初始化第一阶段:

运行至 12 行:this 指向 window,window.name 是浏览器自带的属性它的默认值就是空字符串 "",用来存储窗口名称(用于跨页面通信、iframe 标识)
运行至 13 行:a 的值变为 2

运行至 22 行,有新函数运行,创建新的执行上下文,进入第二步:
第二步:初始化函数 A 的执行上下文,确定其 this 与 VO

运行至 15 行:this 指向新创建的对象 a,将其中的 abc 属性赋值为 123

运行至 20 行,有新函数执行,创建新的执行上下文,进入第三步:
第三步:初始化函数 B 的执行上下文,确定其 this 与 VO

运行至 18 行,输出 window。
B()执行完毕,其上下文出栈,B()执行完毕,A()也执行完毕,至此 22 行 new A()执行完毕,给 a 赋值并将 A()上下文出栈

执行 24 行:a.abc 的值为 123,至此,全部运行结束。
VO 中的值确定的过程:
- 确定所有形参值以及特殊变量arguments
- 确定函数中通过var声明的变量,将它们的值设置为undefined,如果VO中已有该名称,则直接忽略(因为已经是 undefined 了)。
- 确定函数中通过字面量声明的函数(function 的方式,不是变量的方式),将它们的值设置为指向函数对象(函数本身就是个对象),如果VO中已存在该名称,则覆盖。
这里也牵扯到变量与函数的声明提升的问题:
-
var 声明的变量会有声明提升,在 VO 创建时会初始化为 undefined。
-
函数也会有声明提升,也是在 VO 创建时会初始化。
这里也牵扯到变量重新声明的问题 :
var 可以重复声明同一变量,因为 VO 已有该名称,VO 初始化时会忽略后面的声明
js
function test() {
console.log(a); // VO初始化后 a 是 undefined
var a = 100; // 按顺序执行,a 被赋值为 100
console.log(a);
var a = 200; // 按顺序执行,a 被赋值为 200
console.log(a);
}
test(); // undefined 100 200
这里也牵扯到函数重新声明的问题:
函数重新声明,会被覆盖,因为 VO 中将这个函数指向了另一个函数对象。
js
function test() {
console.log(a);
var a = 100;
console.log(a);
var a = 200;
console.log(a);
}
// 上方的 test 会被覆盖
function test() {
console.log(a);
var a = 300;
console.log(a);
var a = 400;
console.log(a);
}
test(); // undefined 300 400
引出作用域链:
当一个执行上下文中的代码执行的时候,如果执行上下文中不存在某个属性,则会从之前的上下文寻找(往栈底找,直到找到全局上下文中,若全局上下文中都没有则报错)
题目:
js
var foo = 1;
function bar() {
console.log(foo);
if (!foo) {
var foo = 10;
}
console.log(foo);
}
bar();
结果:undefined 10
注意点:
var 没有块级作用域,故 bar()的 AO 中也包含 foo,初始化为 undefined
执行到第 4 行时,if 条件为 true,foo 被赋值为 10,故 7 行输出 10
若 5 行改为 let 则连续输出两次 1
题目:
js
var a = 1;
function b() {
console.log(a);
a = 10;
return;
function a() { }
}
b();
console.log(a);
结果:function 1
注意点:
函数声明会被提前,故 b 函数中的 a 初始为 undefined,再后来是 function,再后来是 10
9 行的 a 是全局执行上下文中的 a
题目:
js
var foo = 1;
function bar(a) {
var a1 = a;
var a = foo;
function a() {
console.log(a);
}
a1();
}
bar(3);
结果:1
注意 点:
严格按照赋值顺序来,优先级分别是,变量赋值 undefined,形参赋值,同名变量忽略,同名函数覆盖,然后在按照顺序执行代码。