执行上下文是评估和执行 JavaScript 代码的环境的抽象概念。Javascript 代码都是在执行上下文中运行。javascript
JavaScript 的可执行代码(executable code)的类型只有三种,全局代码、函数代码、eval代码。前端
对应着,JavaScript 中有三种执行上下文类型。java
this
的值等于这个全局对象。一个程序中只会有一个全局执行上下文。eval
函数内部的代码也会有它属于本身的执行上下文举个栗子,当执行到一个函数的时候,就会进行准备工做,这里的“准备工做”,就是准备"执行上下文(execution context)"。git
执行栈,是一种拥有 LIFO(后进先出)数据结构的栈,被用来存储代码运行时建立的全部执行上下文。github
当 JavaScript 开始要解释执行代码的时候,它会建立一个全局的执行上下文而且压入当前执行栈。每当引擎遇到一个函数调用,它会为该函数建立一个新的执行上下文并压入栈的顶部。面试
程序结束以前, 执行栈最底部永远有个全局上下文浏览器
引擎会执行那些执行上下文位于栈顶的函数。当该函数执行结束时,执行上下文从栈中弹出,控制流程到达当前栈中的下一个上下文。数据结构
模拟js执行如下代码:函数
function fun3() {
console.log('fun3')
}
function fun2() {
fun3();
}
function fun1() {
fun2();
}
fun1();
复制代码
定义执行上下文栈:ECStack = [];
post
ECStack.push(globalContext);
ECStack.push(fun1Context);
ECStack.push(fun2Context);
ECStack.push(fun3Context);
ECStack.pop();
ECStack.pop();
ECStack.pop();
建立执行上下文有两个阶段:1) 建立阶段 和 2) 执行阶段。
或者你也能够简单理解为:
因此执行上下文在概念上表示以下:
ExecutionContext = {
ThisBinding = <this value>, LexicalEnvironment = { ... }, VariableEnvironment = { ... }, } 复制代码
在函数执行上下文中,this
的值取决于该函数是如何被调用的。若是它被一个引用对象调用,那么 this
会被设置成那个对象,不然 this
的值被设置为全局对象或者 undefined(严格模式下)
。
词法环境对象
词法环境和变量环境组件始终为 词法环境对象。
变量环境也是一个词法环境,它有着词法环境的全部属性。
在 ES6 中,词法环境组件和变量环境的一个不一样就是前者被用来和变量(let
和 const
)绑定,然后者用来存储函数声明和 var
变量绑定。即:
每一个词法环境对象包含两部分:
如下面代码为例:
let a = 1;
const b = 2;
var c = 3;
function test (d, e) {
var f = 10;
return f * d * e;
}
c = test(a, b);
复制代码
解析阶段的全局环境内的词法环境和变量环境
GlobalLexicalEnvironment = {
LexicalEnvironment: { // 词法环境组件
OuterReference: null, // 全局词法环境中外部引用为空
EnviromentRecord: {
Type: 'object',
a: <uninitialized> , // let 和 const 变量绑定但未关联值
b: <uninitialized>
},
},
VariableEnvironment: { //变量环境组件
EnviromentRecord: {
type: 'object',
test: <func>,
c: undefined, // var变量会被初始为 undefined
}
}
}
复制代码
解析test时的词法环境和变量环境
注意:只有调用函数时,函数执行上下文才会被建立
// 此时 全局上下文已经执行,所以 a、b、c都已经与对应值关联
GlobalLexicalEnvironment = {
LexicalEnvironment: {
OuterReference: null,
EnviromentRecord: {
Type: 'object',
a: 1 ,
b: 2
},
},
VariableEnvironment: {
EnviromentRecord: {
type: 'object',
c: 3,,
test: <func>
}
}
}
// test的词法执行上下文开始构建,var变量绑定但未赋值,形参绑定
FunctionLexicalEnvironment = {
LexicalEnvironment: {
OuterReference: <GlobalLexicalEnvironment>,
EnviromentRecord: {
Type: 'Declarative',
arguments: {0: 1, 1: 2, length: 2}
},
},
VariableEnvironment: {
EnviromentRecord: {
Type: 'Declarative',
f: undefined,
}
}
}
复制代码
插播一条变量提高的知识点:
在建立执行上下文时,js引擎会检查当前做用域的全部变量声明及函数声明,在执行以前,var声明的变量已经绑定初始undefined,而在let和const只绑定在了执行上下文中,但并未初始任何值,因此在声明以前调用则会抛出引用错误(即TDZ暂时性死区),这也就是函数声明与var声明在执行上下文中的提高。
let/const也存在变量提高现象,详情移至你可能不知道的变量提高
在执行上下文的建立阶段,完成了变量声明,在代码的执行阶段,才会完成对变量真正的赋值。
在执行阶段,若是 JavaScript 引擎不能在源码中声明变量的实际位置找到
let
变量的值,它会被赋值为undefined
。
最后,看一个《JavaScript权威指南》中的例子:
var scope = "global scope";
function checkscope(){
var scope = "local scope";
function f(){
return scope;
}
return f();
}
checkscope();
复制代码
var scope = "global scope";
function checkscope(){
var scope = "local scope";
function f(){
return scope;
}
return f;
}
checkscope()();
复制代码
两段代码执行的结果同样,都是
local scope,
若不理解,请移步词法做用域及做用域链讲解
可是两段代码究竟有哪些不一样呢?
模拟第一段代码:
ECStack.push(<checkscope> functionContext);
ECStack.push(<f> functionContext);
ECStack.pop();
ECStack.pop();
复制代码
模拟第二段代码:
ECStack.push(<checkscope> functionContext);
ECStack.pop();
ECStack.push(<f> functionContext);
ECStack.pop();
复制代码
ps: 这篇文章写的很困难,搜集资料的时候被各类词语及讲解弄的很懵,有些逻辑还有冲突,考虑了好久才决定只写这些内容,将这篇文章只做为对执行上下文的简单描述而不是详细讲解,由于再写多了,一些概念会使文章很难被阅读和理解,等后续我有了深刻的理解再更新内容吧。若是有错误之处,欢迎在评论中指出~
相关系列: 从零开始的前端筑基之旅(面试必备,持续更新~)
若是你收获了新知识,请给做者点个赞吧,让更多的人看到它~
参考文章:
- ****JavaScript深刻之执行上下文栈****
- ****[译] 理解 JavaScript 中的执行上下文和执行栈****
- ****也来谈谈JS的执行上下文与词法环境****