深入浅出执行上下文、词法环境、变量环境
2020-11-20 本文已影响0人
灯下草虫鸣314
执行上下文的概念
执行上下文:javascript 代码解析和执行时所在的环境。
执行上下文的类型
执行上下文分为三种类型:
1.全局执行上下文
- js代码开始运行后。首先进入全局执行上下文环境中,不在任何函数中的js代码都会在全局执行上下稳重
- 一个js程序中只存在一个全局执行上下文。创建时会压人栈底,只有当程序结束时才会弹出
- 全局执行上下文会做两件事。1.创建全局对象,2.将this指向这个全局对象
- 浏览器环境中全局对象是window, 在node环境中全局对象是global
2.函数执行上下文
- 函数每次调用都会产生一个新的函数执行上下文,每个函数都拥有自己的执行上下文,但是只有调用的时候才会被创建
- 函数执行上下文的生命周期分为两个阶段。创建和执行
3.Eval执行上下文
- eval函数执行时产生的执行上下文。
执行上下文栈
执行上下文栈是一个后进先出的数据结构,
具体执行流程如下
- 首先创建全局执行上下文, 压入栈底
- 每当调用一个函数时,创建函数的函数执行上下文。并且压入栈顶
- 当函数执行完成后,会从执行上下文栈中弹出,js引擎继续执栈顶的函数。
如以下函数执行时的执行栈变化为:
function fun1(){
console.log('func1')
fun2()
}
function fun2(){
console.log('func2')
}
fun1()
/*
* fun2
* fun1 fun1 fun1
* global => global => global => global => global
*/
执行上下文生命周期
变量对象VO和活动对象AO
在讲生命周期钱。我们必须了解讲个对象,变量对象VO和活动对象AO
变量对象VO:
- 用来存储执行上下文中可以被访问。但不能被delete的函数标识。
- 包括:arguments 对象,形参实参键值对,函数声明,变量声明和this
活动对象AO: - 他可以被理解为当函数被激活调用时创建的对象。用来访问VO对象中存储的那些个标识。
全局上下文中变量对象: - 就是全局对象
- 初始化时:初始化一系列原始属性:Math,String,Date,Window等
- 在浏览器中window对象引用全局对象,全局环境中this也引用自身
创建阶段
此阶段执行上下文会执行以下操作
- 创建作用域链
- 通过变量对象VO创建活动AO,
- 首先创建arguments对象
- 创建形参实参的键值对
- 创建函数声明 (经典面试题:为什么函数声明提前?)
- 创建变量声明 (经典面试题:为什么变量声明提升?)
- 创建this
执行阶段
- 变量赋值。函数引用,执行其他代码逻辑
- 当执行完毕后。执行上下文出栈,等待垃圾回收机制回收
举例说明
function a(name, age){
var a = 1
function c(){}
var d = funciton (){}
(function e(){})
var f = function g(){}
}
a('John')
// 如上代码。
//在创建预编译阶段生成的AO对象如下
AO = {
arguments:{
0: 'John',
1: undefined,
length: 2
},
name: 'John',
age: undefined,
c: reference to function c(){},
a: undefined,
d: undefined,
f: undefined,
}
// 函数表达式 e,不在AO中
// 函数g不在AO中
// 函数执行时AO对象如下
AO = {
arguments:{
0: 'John',
1: undefined,
length: 2
},
name: 'John',
age: undefined,
c: reference to function c(){},
a: 1,
d: reference to FunctionExpression "d",
f: reference to FunctionExpression "f",
}
函数声明提前
从AO对象的创建过程我们就可以发现,AO对象想是先扫描函数体内的函数声明才去扫描变量声明。所以这也就是为啥会有声明提前。
变量提升
AO对象创建时已经将函数内部的变量提前扫描声明。是指在函数执行的过程中开始依次赋值。
词法环境和变量环境
在ES6中提出词法环境和变量环境两个概念。主要是执行上下文创建过程。
词法环境(LexicalEnvironment)
词法环境是一种包含 标识符 => 变量 隐射关系的一种结构。
在词法环境中有两个组成部分:
- 环境记录(EnvironmentRecord): 储存变量和函数声明的实际位置
- 对外部环境的引用(Outer):当前可以访问的外部词法环境
词法环境分为两种类型:
- 全局环境: 全局执行上下文,他没有外部环境的引用,拥有一个全局对象window和关联的方法和属性eg: Math,String,Date等。还有用户定义的全局变量,并将this指向全局对象。
- 函数环境: 用户在函数定义的变量将储存在环境记录中。对外部环境的引用可以是全局环境,也可以是包含内部函数的外部函数环境。环境记录中包含。用户声明的变量。函数。还有arguments对象。
举例词法环境在伪代码中如下:
GlobalExectionContent = {
LexicalEnvironment: {
EnvironmentRecord: {
Type: "Object",
// 剩余标识符
},
Outer: null,
}
}
FunctionExectionContent = {
LexicalEnvironment: {
EnvironmentRecord: {
Type: "Declarative",
// 剩余标识符
},
Outer: [Global or outer function environment reference],
}
}
变量环境(VariableEnvironment)
变量环境也是一个词法环境。他具有词法环境中所有的属性
在ES6中,LexicalEnvironment和VariableEnvironment 的区别在于前者用于存储函数声明和变量let 和 const 绑定,而后者仅用于存储变量 var 绑定。
用以下代码举例:
let a = 20;
const b = 30;
var c;
function add(e, f) {
var g = 20;
function c(){}
return e + f + g;
}
c = add(20, 30);
在预编译阶段。生成的词法环境和变量环境如下
GlobalExectionContent = {
LexicalEnvironment: {
EnvironmentRecord: {
Type: "Object",
a: <uninitialied>,
b: <uninitialied>,
add: <func>
// 剩余标识符
},
Outer: null,
},
VariableEnvironment: {
EnvironmentRecord: {
Type: "Object",
c: undefined,
// 剩余标识符
},
Outer: null,
}
}
FunctionExectionContent = {
LexicalEnvironment: {
EnvironmentRecord: {
Type: "Declarative",
arguments: {
0: 20,
1: 30,
length: 2,
},
e: 20,
f: 30,
c: reference to function c(){}
// 剩余标识符
},
Outer: GlobalLexicalEnvironment,
},
VariableEnvironment: {
EnvironmentRecord: {
Type: "Declarative",
g: undefined,
// 剩余标识符
},
Outer: GlobalLexicalEnvironment,
}
}
我们发现使用let和const声明的变量在词法环境创建时是未赋值初始值。而使用var定义的变量在变量环境创建时赋值为undefined。这也就是为什么const、let声明的变量在声明钱调用会报错,而var声明的变量不会。
代码执行阶段
此阶段的执行流程就是函数执行时的流程。给变量赋值,和执行其他逻辑代码。