什么是执行上下文
执行上下文的概念描述了代码内部是怎么运行的。JavaScript 执行上下文是指允许 JavaScript 代码执行的环境。。变量或函数的上下文决定了它们可以访问那些数据,以及它们的行为。可以把执行上下文想象成一个盒子,盒子内部是我们的代码。
简而言之,执行上下文 (EC) 是 JavaScript 代码运行的环境。所谓环境,是指 JavaScript 代码可以访问的 this、变量、对象和函数的当前值
执行上下文类型
1.全局上下文
是最外层的上下文,根据ECMAScript实现的宿主环境,表示全局上下文的随想可能不一样。在浏览器中全局上下文就是我们说的window对象。
window对象是指或者是连接到全局对象,通常在JS引擎进入全局执行环境前就已创建,包含了了比如localStroage,innerWidth,和事件处理等等属性。
this对象(在全局执行上下文)是一个对象指向window对象。
2.函数上下文
当一个函数被执行或者调用时,将创建一个新的执行上下文。当一个JS引擎看见一个函数被执行,它会为这个函数创建一个局部上下文,默认包含argumentd对象和this对象。
argumnets对象内部是键值对形式存储,并且有一个属性length,表示有多少个参数。当参数为空时length默认为0。
this对象取决于函数是怎么被调用的,如果是通过对象调用,那么this指向那个对象,否则this执行window对象或者“undefined”。
3.eval函数上下文
执行栈
栈是一种后进先出的数据结构,另一方面,JavaScript 执行堆栈是跟踪在脚本生命周期中创建的所有执行上下文的堆栈。
全局执行上下文是默认存在并且在执行栈的最底部,当开始执行全局上下文中的代码,如果JS引擎法相有函数调用,他会为这个函数创建一个函数执行上下文并将它推入执行栈最上方。
JS引擎总是执行在执行栈最上方的执行上下文。当一个执行上下文种的所有代码执行完毕,执行栈会将该执行上下文弹出,接着执行下一上下文,然后如此循环。
创建执行上下文
创建执行上下文分为两步
- Lexical Environment:词法环境组件是一种基于 JavaScript 代码的词法嵌套结构的结构,它定义了标识符与变量和函数值的关联。
- Variable Environment:只定义了标识符和变量之间的关系。
两个之间的不同在于,Lexical Environment存储的是标识符绑定到变量(const和let)和函数,然而Variable Environment简单的存储了标识符绑定到变量(var)。
Environment Record
在每个词法环境种都有一个Environment Record,在词法环境种建立的标识符便规定记录在Environment Record种。
每次评估 JavaScript 代码(var/func 声明或赋值)时都会创建一个新的环境记录,以记录由该代码创建的标识符绑定,在JavaScript种这种环境指作用域。
每个环境记录的 [[OuterEnv]] 字段要么是空的,要么是对外部环境记录的引用。外部环境对象是子函数可以访问父作用域的原因。
当IS引擎在函数内遇到一个变量,他会在当前Environment Record种寻找,如果找不到,他会搜索外部作用域(父Environment Record)再找到之前一直往外找,一直到全局作用域。这个查找过程叫做作用域链。
对于Environment Record有如下几种类型
- **Declarative Environment Record顾名思义,变量、类、模块和/或函数声明都存储在这里。由包含在声明性环境记录范围内的声明定义的标识符集合是绑定的。declarative Environment Record绑定了在作用域中的声明集合。
- Object Environment Record
所有全局作用域中内建的绑定,指向全局对象的window对象是这一种,全局变量和函数被添加到环境中,这也是为什么window.localStorage和window.var的名称可以直接访问。arguments 对象和this对象构成了本地/函数执行上下文中的object Environment Record。 - Global Environment Record
尽管全局环境记录理论上是单个记录,但它被描述为封装object 和declarative Environment Record的组合。它没有外部环境,因为它是 [[OuterEnv]] 为空。用于全局声明。
示例代码创建过程
var name = "Joe";
let input = "Hey!!!";
function broadcast(message) {
return `${name} says ${message}`;
}
console.log(broadcast(input));
- 创建一个全局执行上下文并将其压入执行栈。
- 创建window和全局对象间的绑定。
- 将this指向window对象。this的绑定根据函数怎么被调用或严格模式是否开启而不同。
- 在全局内存中创建
**name**
标识符,并将其赋值为undefined
,这个过程叫做提升(Hoisting) - 创建
input
标识符,没有初始值。 创建broadcast标识符并将整个函数存储,这叫做函数提升。
执行阶段
拿到name的值并将它和存储中的标识符绑定。
- 拿到input的值并将它和存储中的标识符绑定。
- 当遇到console.log,立即评估参数。
- 当遇到broadcast函数执行,构建一个函数上下文并将其压入执行栈。
- broadcast函数执行上下文开始创建阶段。
- 创建
_**arguments**_
对象,length为0。 - 在参数对象的第一个索引处,附加提供的参数
**message**
。 - 创建一个标识符
message
并将赋予函数调用参数的值存储在函数的本地内存中。 - 执行
return
语句。 - 当遇到第一个变量
name
,先在函数内部寻找。 - 在内部找不到,接着到父级作用域寻找。
- 找到后将变量替换为值。
- 在内部查找
message
变量。 - 找到,替换掉变量。
- 执行结束,弹出函数执行上下文。
- 全局上下文接着执行。
- 打印出,Joe says “Hey!!!”。
- 全部执行结束。
GlobalExecutionContext = {
LexicalEnvironment: {
EnvironmentRecord: {
DeclarativeEnvironmentRecord: {
input: "Hey!!!",
broadcast: {
LocalExecutionContext: {
LexicalEnvironment: {
EnvironmentRecord: {
DeclarativeEnvironmentRecord: {
message: "Hey!!!",
},
ObjectEnvironmentRecord: {
arguments: {
0: message,
length: 1
}
this: < ref.to window obj. >
},
},
OuterEnv: < ref.to LexicalEnvironment of the GlobalExecutionContext > ,
},
},
},
},
ObjectEnvironmentRecord: {
window: < ref.to Global obj. > ,
this: < ref.to window Obj. > ,
},
OuterEnv: < null > ,
},
},
VariableEnvironment: {
EnvironmentRecord: {
DeclarativeEnvironmentRecord: {
name: "Joe"
},
},
},
}