一、什么是执行上下文

简单说就是代码运行时的执行环境,必须是在函数调用的时候才会产生,如果不调用就不会产生这个执行上下文。在这个环境中,所有变量会被事先提出来(变量提升),有的直接赋值,有的为默认值 undefined,代码从上往下开始执行,就叫做执行上下文。代码分为三类:全局代码、局部(函数)代码、Eval代码(先不考虑这个),那么也就有三种执行环境,全局执行上下文、函数执行上下文、eval。如图所示:

 全局执行上下文
* 在执行全局代码前将window确定为全局执行上下文
* 对全局数据进行预处理
* var定义的全局变量==>undefined, 添加为window的属性
* function声明的全局函数==>赋值(fun), 添加为window的方法
* this==>赋值(window)
* 开始执行全局代码
函数执行上下文
* 在调用函数, 准备执行函数体之前, 创建对应的函数执行上下文对象(虚拟的, 存在于栈中)
* 对局部数据进行预处理
* 形参变量==>赋值(实参)==>添加为执行上下文的属性
* arguments==>赋值(实参列表), 添加为执行上下文的属性
* var定义的局部变量==>undefined, 添加为执行上下文的属性
* function声明的函数 ==>赋值(fun),
* this==>赋值(调用函数的对象)
* 开始执行函数体代码

二、执行上下文栈

1.在全局代码执行前, JS引擎就会创建一个来存储管理所有的执行上下文对象,每次调用一个方法A的时候,这个方法可能也会调用另一个方法B,B还可能调用方法C,而JS只能同时一件事,所以
方法B、C没执行完之前,方法A也不能被释放,那总得找个地方把这些方法按顺序存一存吧,存放的地方就是执行上下文栈,也叫调用栈。
1. 在全局代码执行前, JS引擎就会创建一个来存储管理所有的执行上下文对象
2. 在全局执行上下文(window)确定后, 将其添加到栈中(压栈),(最底部)
3. 在函数执行上下文创建后, 将其添加到栈中(压栈)
4. 在当前函数执行完后,将栈顶的对象移除(出栈)
5. 当所有的代码执行完后, 栈中只剩下window

以下面这个函数举例子:

画的有点丑

这个js代码整体有三个执行上下文,window、bar()函数、foo()函数,js代码在第一次加载的时候会先创建一个全局的window执行上下文,如果代码中有函数调用,就创建一个该函数的执行上下文,并将这个上下文推入到执行栈顶,在当前bar函数中又调用了一个函数,继续创建foo函数的执行上下文,并将该上下文推入栈顶,浏览器会一直执行当前栈顶的执行上下文,一旦函数执行完毕,该上下文就会被推出执行栈,直到最后剩一个全局执行上下文。如上图。

思考:假如bar函数中同时调用了两个函数foo1和foo2,那么这种情况下,栈中会有几个执行上下文?

依然是只有三个,因为执行foo1函数的时候foo2函数并不会推入栈中,直到foo1函数执行完毕,此时foo1会被推出栈,再加入foo2函数,,所以要牢记,函数执行完就会被弹出栈。

三、执行上下文的三个阶段

1.创建阶段

      (1).生成变量对象

      (2).建立作用域链

      (3).确定 this 指向

在全局执行上下文中,this总是指向全局对象,例如浏览器环境下this指向window对象。

而在函数执行上下文中,this的值取决于函数的调用方式,如果被一个对象调用,那么this指向这个对象。否则this一般指向全局对象window或者undefined(严格模式)。

    2.执行阶段

      (1).变量赋值

      (2).函数引用

      (3).执行其他代码

    3.销毁阶段

      执行完毕出栈,等待回收被销毁

以下面这个函数作为例子:

console.log(this)

  function fn (a1) {
console.log(a1)
console.log(a2)
a3()
console.log(this)
console.log(arguments)
var a2 = 4
function a3 () {
console.log('a3()')
}
}
fn(2, 3) //调用

  • 首先JS解释器(引擎)开始解释代码,构建执行环境栈(Execution Context Stack),并根据执行环境的不同生成不同的执行上下文(Execution Context)

  • 栈底永远是全局上下文,当遇到fn(),确认调用函数,就创建生成fn自己的上下文,然后将函数执行上下文入栈(push on)到执行环境栈中。

  • 当函数执行完,弹出栈,销毁

再来看一个例子:

let a = 20;
const b = 30;
var c; function multiply(e, f) {
var g = 20;
return e * f * g;
} c = multiply(20, 30);

我们用伪代码来描述上述代码中执行上下文的创建过程:

//全局执行上下文
GlobalExectionContext = {
// this绑定为全局对象
ThisBinding: <Global Object>,
// 词法环境
LexicalEnvironment: {
//环境记录
EnvironmentRecord: {
Type: "Object", // 对象环境记录
// 标识符绑定在这里 let const创建的变量a b在这
a: < uninitialized >,
b: < uninitialized >,
multiply: < func >
}
// 全局环境外部环境引入为null
outer: <null>
}, VariableEnvironment: {
EnvironmentRecord: {
Type: "Object", // 对象环境记录
// 标识符绑定在这里 var创建的c在这
c: undefined,
}
// 全局环境外部环境引入为null
outer: <null>
}
} // 函数执行上下文
FunctionExectionContext = {
//由于函数是默认调用 this绑定同样是全局对象
ThisBinding: <Global Object>,
// 词法环境
LexicalEnvironment: {
EnvironmentRecord: {
Type: "Declarative", // 声明性环境记录
// 标识符绑定在这里 arguments对象在这
Arguments: {0: 20, 1: 30, length: 2},
},
// 外部环境引入记录为</Global>
outer: <GlobalEnvironment>
}, VariableEnvironment: {
EnvironmentRecord: {
Type: "Declarative", // 声明性环境记录
// 标识符绑定在这里 var创建的g在这
g: undefined
},
// 外部环境引入记录为</Global>
outer: <GlobalEnvironment>
}
}

在执行上下文创建阶段,函数声明与var声明的变量在创建阶段已经被赋予了一个值,var声明被设置为了undefined,函数被设置为了自身函数,而let  const被设置为未初始化。

现在你总知道变量提升与函数提升是怎么回事了吧,以及为什么let const为什么有暂时性死域,这是因为作用域创建阶段JS引擎对两者初始化赋值不同。

上下文除了创建阶段外,还有执行阶段,这点大家应该好理解,代码执行时根据之前的环境记录对应赋值,比如早期var在创建阶段为undefined,如果有值就对应赋值,像let const值为未初始化,如果有值就赋值,无值则赋予undefined。

五、练习

想想这段代码的输出顺序是什么

console.log('gb: '+ i)
var i = 1
foo(1)
function foo(i) {
if (i == 4) {
return
}
console.log('fb:' + i)
foo(i + 1) //递归调用: 在函数内部调用自己
console.log('fe:' + i)
}
console.log('ge: ' + i)

这是一个递归调用,在不满足条件的情况下函数会一直调用自己,直到条件满足函数就结束调用,继续上图:

很多人都觉得fe输出3就结束了,怎么还会有2和1,其实return只是结束了最后一次调用,没有执行完的代码会按出栈顺序执行完。

所以依次输出为:

gb: undefined
fb: 1
fb: 2
fb: 3
fe: 3
fe: 2
fe: 1
ge: 1

最新文章

  1. [高性能MYSQL 读后随笔] 关于事务的隔离级别(一)
  2. gridview安卓实现单行多列横向滚动
  3. xml 解析的四种方式
  4. 唯一区别是不会去取emptyText 的值,没有选选择选项的时候返回是空字符串
  5. C++ eof()函数相关应用技巧分享
  6. 手把手教popupWindow从下往上,以达到流行效果
  7. mybaits错误解决:There is no getter for property named &#39;parentId &#39; in class &#39;java.lang.String&#39;
  8. 【有上下界的网络流】ZOJ2341 Reactor Cooling(有上下界可行流)
  9. Java大数相加-hdu1047
  10. js封装Cookie操作 js 获取cookie js 设置cookie js 删除cookie
  11. 实现一个键对应多个值的字典(multidict)
  12. C语言之网络编程(服务器和客户端)
  13. Web服务架构风格之REST
  14. Session[&quot;Write&quot;] = &quot;Write&quot;;
  15. day 21 今日学习内容
  16. Leetcode题库——20.有效的括号
  17. FZU 1901 Period II(KMP循环节+公共前后缀)
  18. 《官方资料》 例如:string 函数 、分组函数
  19. jquery 事件对象属性小结
  20. 第6课:datetime模块、操作数据库、__name__、redis、mock接口

热门文章

  1. 模拟赛DAY1 T2腐草为萤
  2. ImageView的src与background及ScaleType
  3. drf 视图源码详解
  4. python进行数据库迁移的时候显示(TypeError: __init__() missing 1 required positional argument: &#39;on_delete&#39;)
  5. 一个DRF框架的小案例
  6. postman的断言/环境变量的处理
  7. Noi2018 归途
  8. vue子组件修改父组件传递过来的值
  9. ECharts-第一篇最简单的应用
  10. 微信小程序 Mustache语法详解