javaScript中的执行栈和执行上下文

执行上下文

执行上下文,英文全称为 Execution Context,一句话概括就是“代码(全局代码、函数代码)执行前进行的准备工作”,也称之为“执行上下文环境”。

运行JavaScript代码,当代码执行进入一个环境时,就会为该环境创建一个执行上下文,它会在你运行代码前做一些准备工作,如确定作用域、创建局部变量对象等。

JavaScript 执行环境

  1. 全局环境
  2. 函数环境
  3. eval 函数环境 (已不推荐使用)

那么与之对应的执行上下文类型同样有 3 种:

  1. 全局执行上下文:这是默认或者说基础的上下文,任何不在函数内部的代码都在全局上下文中。它会执行两件事,创建一个全局的 window 对象(浏览器的情况下),并且设置 this 的值等于这个全局对象。一个程序中只会有一个全局执行上下文。
  2. 函数执行上下文:每当一个函数被调用时, 都会为该函数创建一个新的上下文。每个函数都有它自己的执行上下文,不过是在函数被调用时创建的。函数上下文可以有任意多个。每当一个新的执行上下文被创建,它会按定义的顺序(将在后文讨论)执行一系列步骤。
  3. eval 函数执行上下文:执行在 eval 函数内部的代码也会有它属于自己的执行上下文。

JavaScript 运行时首先会进入全局环境,对应会生成全局上下文。程序代码中基本都会存在函数,那么调用函数,就会进入函数执行环境,对应就会生成该函数的执行上下文。

由于代码中会声明多个函数,对应的函数执行上下文也会存在多个。在 JavaScript 中,通过栈的存取方式来管理执行上下文,我们可称其为执行栈,或函数调用栈(Call Stack)。

栈数据结构

要简单理解栈的存取方式,我们可以通过类比乒乓球盒子来分析。如下图:

在这里插入图片描述

栈遵循**“先进后出,后进先出”**的规则,或称 LIFO (”Last In First Out“)规则。

栈中“放入/取出”,也可称为“入栈/出栈”。

总结栈数据结构的特点:

  1. 后进先出,先进后出
  2. 出口在顶部,且仅有一个

执行栈(函数调用栈)

JavaScript 中如何通过栈来管理多个执行上下文?

调用栈是解析器(如浏览器中的的 JavaScript 解析器)的一种机制,可以在脚本调用多个函数时,跟踪每个函数在完成执行时应该返回控制的点。(如什么函数正在执行,什么函数被这个函数调用,下一个调用的函数是谁)

程序执行进入一个执行环境时,它的执行上下文就会被创建,并被推入执行栈中(入栈);程序执行完成时,它的执行上下文就会被销毁,并从栈顶被推出(出栈),控制权交由下一个执行上下文。

因为 JavaScript 在执行代码时最先进入全局环境,所以处于栈底的永远是全局环境的执行上下文。而处于栈顶的是当前正在执行函数的执行上下文

当函数调用完成后,它就会从栈顶被推出,理想的情况下,闭包会阻止该操作。
闭包相关参考:JS堆栈执行与闭包

而全局环境只有一个,对应的全局执行上下文也只有一个,只有当页面被关闭之后它才会从执行栈中被推出,否则一直存在于栈底。

代码示例:

function foo () { function bar () {        return 'I am bar';}return bar();
}
foo();

对应图解如下:

在这里插入图片描述

执行上下文的数量限制(堆栈溢出)

执行上下文可存在多个,虽然没有明确的数量限制,但如果超出栈分配的空间,会造成堆栈溢出。常见于递归调用,没有终止条件造成死循环的场景。

// 递归调用自身
function foo() {foo();
}
foo();
// 报错: Uncaught RangeError: Maximum call stack size exceeded

执行上下文生命周期

运行 JavaScript 代码时,当代码执行进入一个环境时,就会为该环境创建一个执行上下文,它会在你运行代码前做一些准备工作。

具体要做的事,和执行上下文的生命周期有关。

执行上下文的生命周期有两个阶段:

  1. 创建阶段(进入执行上下文):函数被调用时,进入函数环境,为其创建一个执行上下文,此时进入创建阶段。
  2. 执行阶段(代码执行):执行函数中代码时,此时执行上下文进入执行阶段。

创建阶段

创建阶段要做的事情主要如下:

  1. 创建变量对象(VO:variable object
    • 确定函数的形参(并赋值
    • 函数环境会初始化创建 Arguments对象(并赋值
    • 普通字面量形式的函数声明(并赋值
    • 变量声明、函数表达式声明(未赋值
  2. 确定this指向(this 由调用者确定
  3. 确定作用域(词法环境决定,哪里声明定义,就在哪里确定

关于变量对象,当处于执行上下文的建立阶段时,我们可以将整个上下文环境看作是一个对象。该对象拥有 3 个属性,如下:

executionContextObj = {variableObject : {}, // 变量对象,里面包含 Arguments 对象,形式参数,函数和局部变量scopeChain : {},// 作用域链,包含内部上下文所有变量对象的列表this : {}// 上下文中 this 的指向对象
}

可以看到,这里执行上下文抽象成为了一个对象,拥有 3 个属性,分别是变量对象作用域链以及 this 指向,这里我们重点来看一下变量对象里面所拥有的东西。

在函数的建立阶段,首先会建立 Arguments 对象。然后确定形式参数,检查当前上下文中的函数声明,每找到一个函数声明,就在 variableObject 下面用函数名建立一个属性,属性值就指向该函数在内存中的地址的一个引用。

如果上述函数名已经存在于 variableObject(简称 VO) 下面,那么对应的属性值会被新的引用给覆盖。

最后,是确定当前上下文中的局部变量,如果遇到和函数名同名的变量,则会忽略该变量

执行阶段

  1. 变量对象赋值
    • 变量赋值
    • 函数表达式赋值
  2. 调用函数
  3. 顺序执行其他代码

接下来我们来通过代码来演示一下这两个阶段做的每一件事以及变量对象是如何变化的。

const foo = function(i){var a = "Hello";var b = function privateB(){};function c(){}
}
foo(10);

首先在建立阶段的变量对象如下:

fooExecutionContext = {variavleObject : {arguments : {0 : 10,length : 1}, // 确定 Arguments 对象i : 10, // 确定形式参数c : pointer to function c(), // 确定函数引用a : undefined, // 局部变量 初始值为 undefinedb : undefined  // 局部变量 初始值为 undefined},scopeChain : {},this : {}
}

由此可见,在建立阶段,除了 Arguments,函数的声明,以及形式参数被赋予了具体的属性值外,其它的变量属性默认的都是 undefined。并且普通形式声明的函数的提升是在变量的上面的。

一旦上述建立阶段结束,引擎就会进入代码执行阶段,这个阶段完成后,上述执行上下文对象如下,变量会被赋上具体的值。

fooExecutionContext = {variavleObject : {arguments : {0 : 10,length : 1},i : 10,c : pointer to function c(),a : "Hello",// a 变量被赋值为 Hellob : pointer to function privateB() // b 变量被赋值为 privateB() 函数},scopeChain : {},this : {}
}

我们看到,只有在代码执行阶段,局部变量才会被赋予具体的值。在建立阶段局部变量的值都是 undefined

这其实也就解释了变量提升的原理。

接下来我们再通过一段代码来加深对函数这两个阶段的过程的理解,代码如下:

(function () {console.log(typeof foo);console.log(typeof bar);var foo = "Hello";var bar = function () {return "World";}function foo() {return "good";}console.log(foo, typeof foo);
})()

这里,我们定义了一个 IIFE,该函数在建立阶段的变量对象如下:

fooExecutionContext = {variavleObject : {arguments : {length : 0},foo : pointer to function foo(),bar : undefined},scopeChain : {},this : {}
}

首先确定 Arguments 对象,接下来是形式参数,由于本例中不存在形式参数,所以接下来开始确定函数的引用,找到 foo 函数后,创建 foo 标识符来指向这个 foo 函数,之后同名的 foo 变量不会再被创建,会直接被忽略。

然后创建 bar 变量,不过初始值为 undefined

建立阶段完成之后,接下来进入代码执行阶段,开始一句一句的执行代码,结果如下:

(function () {console.log(typeof foo); // functionconsole.log(typeof bar); // undefinedvar foo = "Hello"; // foo 被重新赋值 变成了一个字符串var bar = function () {return "World";}function foo() {return "good";}console.log(foo, typeof foo); //Hello string
})()

结语

简而言之,执行上下文是评估和执行 JavaScript 代码的环境的抽象概念。每当 Javascript 代码在运行的时候,它都是在执行上下文中运行。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/641396.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

COZY: Stylized Weather 3

远离枯燥的现实主义,一个精美手工制作的程式化天气系统。 从一天中的不同时间到一年中的不同季节,从蓬松的云朵到狂风暴雨,从晴朗的日子到浓雾弥漫,COZY都能轻松做到! ✅ 3.0中的新功能 HDRP支持 物理上精确的月球系统 8K 星形纹理 新的豪华云风格融合了纹理,创造出高性…

Facebook的区块链应用深度分析

去中心化身份验证的意义 在当今数字化社会中,身份验证的重要性不言而喻。对于Facebook这样的大型社交媒体平台来说,确保用户的身份真实性和数据的安全性是至关重要的。传统的中心化身份验证方式存在一定的安全风险和可信性问题,而去中心化身…

5款小伙伴们私信推荐免费软件

​ 最近后台收到好多小伙伴的私信,今天继续推荐五款小工具,都是免费使用的,大家可以去试试看。 1. 数据恢复工具——EaseUS Data ​ EaseUS Data是一款高效的数据恢复软件,能够恢复因各种原因丢失的文件,如误删除、格…

Redis入门到通关之数据结构解析-ZipList

文章目录 ☃️概述☃️ZipListEntry☃️Encoding编码☃️ZipList的连锁更新问题☃️总结 欢迎来到 请回答1024 的博客 🍓🍓🍓欢迎来到 请回答1024的博客 关于博主: 我是 请回答1024,一个追求数学与计算的边界、时间与…

街子智勇传媒,盼你知错即改

今天本“人民体验官”推荐人民日报官方微博文化产品《请相信:读书是通向世界最好的路》。 图:来源“人民体验官”推广平台 “不读书,天地辽阔内心犹在井底。”这话说得入木三分! 这话让笔者立即联想到自己最近发表的戏作打油诗《…

RF高频腔设计(6)

为了在最小功率损耗的情况下得到最大的加速电压,我们需要最大化分路阻抗(shunt impedance)。 由公式 R ( R Q ) Q R\left(\frac{R}{Q}\right)Q R(QR​)Q 可知道,如果增加分路阻抗的大小,可以通过提高Q,或…

在数字化转型过程中,企业的资产管理需要做出哪些调整?

在数字化转型过程中,企业的资产管理做出调整的常见于以下几个方面: 1、提高工作效率:数字化转型能够让员工在部门与部门之间的沟通更加顺畅,节省时间,提高效率。这要求企业在资产管理中采用数字化工具和流程&#xff…

得帆云X银雁科技,低代码将在数字化转型中发挥更大作用

1996年,银雁科技服务集团股份有限公司于深圳成立,服务网络覆盖全国200多个城市。在科技服务领域深耕二十余年,以数字技术基础服务设施,为客户提供业务流程服务,基于多维度的产品组件,可根据不同行业客户需求…

Qt中连接mysql

1、安装mysql,workbench,为mysql添加环境变量 2、安装Qt带src,然后到如下目录,找到mysql.pro(建议做个副本先) http://D:\Qt\Qt5.13.2\5.13.2\Src\qtbase\src\plugins\sqldrivers\mysql mysql.pro 注意路径的 \ / 和双引号的使…

java:Java中的异常处理

目录 异常的概念与体系结构 异常的概念: 异常的体系结构: 异常的处理方式 防御式编程: 异常的抛出: 异常的捕获: finally: 代码示例: 异常的处理流程 自定义异常类 举例&#xff1a…

管理 Python 项目的艺术:在 PyCharm 中使用虚拟环境(以BPnP为例)

在 PyCharm 中使用虚拟环境对于 Python 项目开发具有多方面的重要作用,这些作用体现在提升项目管理的效率、保障代码的可运行性以及维护项目的长期稳定性等方面。以下是使用虚拟环境的几个关键好处: 1. 依赖管理和隔离 虚拟环境允许每个项目拥有…

污水处理设备运维注意事项有哪些

污水处理设备的运维是确保污水处理效率和处理质量的关键环节。良好的运维不仅可以延长设备的使用寿命,还能确保污水处理过程的稳定性和可靠性。以下是一些污水处理设备运维的重要注意事项: 1. 定期检查和维护 设备检查:定期对污水处理设备进…