一、什么是执行上下文

简单说就是代码运行时的执行环境,必须是在函数调用的时候才会产生,如果不调用就不会产生这个执行上下文。在这个环境中,所有变量会被事先提出来(变量提升),有的直接赋值,有的为默认值 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

js执行上下文与执行上下文栈的更多相关文章

  1. js基础梳理-究竟什么是执行上下文栈(执行栈),执行上下文(可执行代码)?

    日常在群里讨论一些概念性的问题,比如变量提升,作用域和闭包相关问题的时候,经常会听一些大佬们给别人解释的时候说执行上下文,调用上下文巴拉巴拉,总有点似懂非懂,不明觉厉的感觉.今天,就对这两个概念梳理一 ...

  2. js的基础(平民理解的执行上下文/调用堆栈/内存栈/值类型/引用类型)

    与以前的切图比较,现在的前端开发对js的要求似乎越来越高,在开发中,我们不仅仅是要知道如何运用现有的框架(react/vue/ng), 而且我们对一些基础的知识的依赖越来越大. 现在我们就用平民的方法 ...

  3. 一文弄懂js的执行上下文与执行上下文栈

    目录 执行上下文与执行上下文栈 变量提升与函数提升 变量提升 函数提升 变量提升与函数提升的优先级 变量提升的一道题目引出var关键字与let关键字各自的特性 执行上下文 全局执行上下文 函数(局部) ...

  4. 【机制】js的闭包、执行上下文、作用域链

    1.从闭包说起 什么是闭包 一个函数和对其周围状态(词法环境)的引用捆绑在一起,这样的组合就是闭包. 也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域. 在 JavaScript 中,每 ...

  5. JS进阶系列之执行上下文

    function test(){ console.log(a);//undefined; var a = 1; } test(); 也许你会遇到过上面这样的面试题,你只知道它考的是变量提升,但是具体的 ...

  6. javascript执行环境(执行期上下文)详解

    javascript执行环境(执行期上下文) 当js控制器(control)进入可执行代码时,控制器会进入一个执行环境,活动的多个执行环境构成执行环境栈,最上面的是正在运行的执行环境,当控制器进入一个 ...

  7. JavaScript 执行环境(执行上下文) 变量对象 作用域链 上下文 块级作用域 私有变量和特权方法

    总结自<高程三>第四章  理解Javascript_12_执行模型浅析   JS的执行环境与作用域  javascript高级程序第三版学习笔记[执行环境.作用域] 在javascript ...

  8. 执行上下文与同步上下文 | ExecutionContext 和 SynchronizationContext

    原文连接:执行上下文与同步上下文 - .NET 并行编程 (microsoft.com) 执行上下文与同步上下文 斯蒂芬 6月15日, 2012 最近,我被问了几次关于 ExecutionContex ...

  9. JS引擎线程的执行过程的三个阶段(一)

    浏览器首先按顺序加载由<script>标签分割的js代码块,加载js代码块完毕后,立刻进入以下三个阶段,然后再按顺序查找下一个代码块,再继续执行以下三个阶段,无论是外部脚本文件(不异步加载 ...

随机推荐

  1. P4999烦(gui)人(chu)的数学作业

    P4399P4999 这是一道有着三倍经验的宝藏题目 我们可以求出来1到n中,1~9分别出现了几次,设f[i]为数字i出现的次数,则\(ans=\sum{f[i]\cdot i}\) 然后就是数位dp ...

  2. 职位-CFO:CFO

    ylbtech-职位-CFO:CFO 首席财务官——CFO(Chief Finance Officer)是企业治理结构发展到一个新阶段的必然产物.没有首席财务官的治理结构不是现代意义上完善的治理结构. ...

  3. 转战 rocketmq

    接触 kafka 有一段时间了,一个人的力量实在有限,国内 rocketmq 的生态确实更好,决定换方向. rocketmq 文档地址:http://rocketmq.cloud/zh-cn/docs ...

  4. 阶段1 语言基础+高级_1-3-Java语言高级_06-File类与IO流_06 Properties集合_1_使用Properties集合存储数据,遍历取出集合中的数据

    map下面的实现类叫做Hashtable Properties是唯一和IO流相结合的 讲解 代码

  5. GMSSL中生成SM2或RSA1024或RSA2048的证书相关命令

    1.生成KEY:gmssl sm2 -genkey -out 01.root.pemgmssl genrsa -out 01.root_plain.key 2048gmssl genrsa -out ...

  6. 4 cdh 5.12 centos 6.10三节点安装

    4 cdh 5.12  centos 6.10 三节点安装 [root@hadoop1 opt]# cat /etc/redhat-release CentOS release 6.10 (Final ...

  7. usb接口类型 简单分类辨识

    usb接口类型 简单分类辨识 - [相似百科] 庆欣 0.0 4 人赞同了该文章 1. 先放图,随着越来越多的接触智能设备,会遇到各种各样的usb接口,对于很多人来说,接口类型只有:usb接口,安卓接 ...

  8. 【ABAP系列】SAP ABAP选择屏幕(SELECTION SCREEN)事件解析

    公众号:SAP Technical 本文作者:matinal 原文出处:http://www.cnblogs.com/SAPmatinal/ 原文链接:[ABAP系列]SAP ABAP选择屏幕(SEL ...

  9. vue子组件修改父组件传递过来的值

    这里不再赘述父子组件及子父组件传值,不懂的同学可以翻看我以前写过的关于两者传值的文章 父子组件传值:https://www.cnblogs.com/Sky-Ice/p/9267192.html 子父组 ...

  10. Node.js实战8:可用于压缩、加密的zlib。

    zlib是nodejs内置的模块,有deflate.inflate函数,使用的是gzip算法,可用于压缩和解压,也可用于数据加密.解密. 如下示例: var zlib = require(" ...