JS执行环境(执行上下文 ) ES3版本

简介: 执行环境(execution context) 中的 context 有着上下文和环境两层含义。


执行环境(execution context) 中的 context 有着上下文和环境两层含义。

在JS中,对象是一个存储系统,是一系列键值对的集合,通过某个键也就是属性名,可以引用到它的值。这些值可能是普通的字符串或数字,也可能是对象。如果是函数对象,那么可称为该对象的方法。而JS中的函数是一个执行系统,函数可能是某个对象的属性,当它执行时,可以说这个_对象是这个函数的对象上下文_。函数的对象上下文是可以变化的,这取决于函数的对象上下文,也就是this的值

当函数执行时,会进入一个特定的执行环境或叫做执行上下文,要明白执行上下文和对象上下文完全是不同的概念。执行上下文是在函数执行时创建的,每次进入函数都会创建一个完全不同的执行环境,也就是说执行环境是动态的

在JS中,可执行的JS代码分为三种类型,它们分别对应不同类型的执行环境。

  • Global Code:全局的、不在任何函数内的代码
  • Eval Code:使用eval()函数动态执行的JS代码
  • Function Code:用户自定义函数中函数体内JS代码

不同类型的JS代码具有不同的执行环境,所有的JS代码都是在一个执行环境中被执行的。执行环境是一种概念,同时也是一种机制。用来完成JS运行时在_作用域、生存期_等方面的处理。执行环境定义了变量或函数是否具有访问其他数据的权限,进而决定各自行为。

其中全局环境只有一个,且一直存在,而函数环境是_进入函数时_创建的。JS实现为单线程,因此同一时刻仅有一个环境处于运行状态。因此,可以把JS的执行实现为栈,每次进入一个新的执行环境,都会把该环境置于栈顶执行,执行完毕后可弹出。因此形成了一个执行上下文栈。而变量的查找正是基于该栈的。

每个执行环境都有一个与之关联的变量对象(variable object),环境中定义的所有变量和函数都保存在这个对象中。虽然编写的代码无法访问到这个对象,但解析器在处理数据时会在后台使用它。

执行环境(执行上下文)组成

当JS代码执行时,就会进入不同的执行环境,每个执行环境由三部分构成:

  • 变量对象(VO, variable object):变量对象即包含变量的对象,开发者无法直接访问。
  • 作用域属性:[[Scope]]属性时一个指向_单向链表的头结点_的指针
    作用域即变量对象,作用域链是一个由变量对象组成的带头结点的单向链表,作用是用来进行变量查找。
  • this:指向一个环境对象

执行环境和作用域是完全不同的概念。从根本上来讲,作用域是基于函数的,而执行环境是基于对象的。换句话说,作用域涉及到被调用函数中的变量访问,而且不同调用场景是不一样的。执行环境始终是this关键字的值,它是拥有当前所执行代码的对象的引用。每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中。虽然编码中无法访问这个对象,但解析器在处理数据时会在后台使用它。

执行环境(执行上下文)分类

  • 全局执行环境
    在浏览器环境中全局执行环境就是window对象,window对象是JS代码开始运行时的默认环境。全局执行环境的变量对象始终都是作用域中的最后一个对象
  • 函数执行环境
    当某函数被调用时,首先会创建一个执行环境及相应的作用域链,然后使用**arguments和其他命名参数的值来初始化**执行环境的变量对象

当Web页面中第一次载入JS代码时,会创建一个全局执行环境(window对象),所有全局变量和函数都作为window对象的属性和方法而创建。

当Web页面中第一次载入JS代码时,会创建一个全局执行环境(window对象),所有全局变量和函数都作为window对象的属性和方法而创建。

全局执行环境是最外围的一个执行环境,根据ECMAScript实现所在宿主环境不同,表示执行环境的对象也不一样。在Web浏览器中,全局执行环境被认为**window对象**,因此所有全局变量和函数都是作为window对象的属性和方法创建。某个执行环境中的所有代码执行完毕后,该环境被销毁,保存在其中的所有变量和函数定义也随之销毁(全局执行环境直到应用程序退出时才会被销毁,例如关闭网页或浏览器时。)

函数执行环境(局部执行环境)是函数执行执行过程中创建的。作用域链是基于执行环境的变量对象的,由所有执行环境的变量对象组成的。对于函数而言变量对象就是活动对象,因为在函数执行环境中,变量对象是不能直接访问的,此时由活动对象扮演变量对象的角色。当代码在一个环境中执行时,会创建变量的作用域链,作用域链的作用在于保证执行环境有权访问的变量和函数的有序访问。作用域的前端,始终都是_当前执行的代码_所在环境的变量对象

当JS代码执行时,JS解释器会通过两个阶段产生一个执行环境:

  1. 1. 创建阶段:当函数被调用时,尚未执行函数内部代码之前。
  • 创建变量对象
  • 设置作用域属性的值
  • 设置this的值
  1. 2. 激活阶段:代码执行阶段
  • 初始化变量对象:设置变量的值、函数的引用
  • 解释并执行代码

访问变量详细流程

创建变量对象

创建变量对象的流程如下

  1. 1. 根据函数的参数,创建并初始化arguments对象。
  2. 2. 声明提升
    2.1. 扫描函数内部代码,查找函数声明
    查找所有函数声明将函数名和函数引用存入变量对象中,如果变量对象中已存在同名函数则覆盖。
    2.2. 扫描函数内部代码查找变量声明
    查找所有变量声明,将变量名存入变量对象中,初始化为undefined。如果变量名和已声明的形参或函数同名,则什么也不做。

函数执行环境

每个函数都有自己的执行环境

定义期

函数定义时,会创建一个**[[Scope]]属性,[[Scope]]对象对应的是一个对象的列表**,列表中的对象仅供JS内部访问,无法通过语法访问。

执行期

调用一个JS函数时,函数就会_进入_与该函数_相对应_的执行环境。如果又调用了另外一个函数,或者递归地调用同一个函数,则又会创建一个新的执行环境,在函数调用期间执行过程都处于该环境中。当调用的函数返回后,执行过程会返回原始执行环境。因此,运行中的JS代码就构成了一个执行环境****栈

简单来说,当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。函数执行之后,栈将其环境弹出,把控制权返回给之前的执行环境。ECMAScript程序中的执行流正是由这个方便的机制控制着。

环境栈:当JS代码执行的时候会进入不同的执行上下文,这些执行上下文会构成一个执行上下文栈(ECS, execution context stack)


具体来说,当代码在一个环境中执行时,会创建变量对象的一个作用域链(scope chain)。作用域链的用途,是保证对执行环境有权访问的所有变量和函数的有序访问。作用域链的前端,始终都是当前执行的代码所在环境的变量对象。如果这个环境是函数,则将其**活动对象(activation object)**作为变量对象。作用域链中的下一个变量对象来自包含(外部)环境,而再下一个变量对象则来自下一个包含环境。这样,一直延续到全局执行环境。全局执行环境的变量对象始终都是作用域链中的最后一个对象

换句话说,程序在进入每个执行环境时,JS引擎在内部创建一个对象,叫做变量对象。对应到函数的每个参数时,在变量对象上会添加一个属性,属性名和值与参数名和值相同。函数中每声明一个局部变量,也会在变量对象上添加一个属性,属性名就是变量名,因此为变量赋值就是给变量对象属性赋值。在函数中访问参数或局部变量时,就是在变量对象上搜索对应的属性并返回其值。一般来说,变量对象是一个内部对象,JS代码中是无法直接访问的。ECMAScript规范中对其实现方式并不做要求,因此变量对象可能只是JS引擎内部的一种数据结构。

JS引擎将不同执行位置上的变量对象按照规则构建了一个链表,在访问一个变量时,先在链表的第一个变量对象上查找,如果没有找到则继续在第二个环境变量上查找,直到搜索结束。这就是作用域链的基本概念。

作用域链是一个变量对象的链表

访问标识符

当执行JS代码时遇到标识符,就会根据标识符的名称,在运行时上下文的_作用域链_中进行搜索。从作用域的第一个对象开始(如函数的活动对象)查找,如果没有找到,就搜索作用域链中下一个对象,如此往复直到找到标识符的定义。如果在搜索完作用域中的最后一个对象(全局对象)以后也没有找到,则会抛出一个错误,提示undefined


简单来说,标识符解析是沿着作用域链一级一级地搜索标识符的过程,搜索过程始终从作用域链的前端开始,然后逐级地向后回溯,直至找到标识符为止,如果找不到标识符,通常会导致错误发生。

var color = 'red';
function changeColor(){
    var localColor = 'green';
    function swapColor(){
        var tmpColor = localColor;
        localColor = color;
        color = tmpColor;
    }
    swapColor();
}
changeColor();
console.log(color);// green
代码涉及3个运行环境:
1. 全局环境
    |- 变量color
    |- 函数changeColor()
2. changeColor()局部环境:可访问全局环境中的变量
    |- 变量localColor
    |- 函数swapColor()
3. swapColor() 局部环境
    |- 变量 tmpColor:当前环境中才能访问,其他环境无权访问。
作用域链结构
window 全局环境
  |- color
  |- changeColor() 局部环境
    |- localColor
    |- swapColor() 局部环境
      |- tmpColor
swapColor()内部可访问其他两个环境中的所有变量,因为它们是它的父执行环境。
对于swapColor()而言,其作用域链上包含3个对象:
|- swapColor()的变量对象
|- changeColor()的变量对象
|- 全局变量对象
swapColor()的局部环境开始时会先在自己的变量对象中搜索变量和函数名,如果搜索不到则搜索上一级作用域。
changeColor()的作用域链中只包含两个对象:自己的变量对象和全局对象,所以它不能访问swapColor()的环境。
复制代码

函数参数被当做变量对象,其访问规则与执行环境的变量相同。

相关文章
|
1月前
|
存储 JavaScript 前端开发
深入理解 JavaScript 执行上下文与 this 绑定机制
JavaScript 代码执行时,会为每段可执行代码创建对应的执行上下文,其中包含三个重要属性:变量对象、作用域链、和 this。本文深入剖析了执行上下文的生命周期以及 this 在不同情况下的指向规则。通过解析全局上下文和函数上下文中的 this,我们详细讲解了 this 的运行期绑定特性,并展示了如何通过调用方式影响 this 的绑定对象。同时,文中对箭头函数 this 的特殊性以及四条判断 this 绑定的规则进行了总结,帮助开发者更清晰地理解 JavaScript 中的 this 行为。
72 8
深入理解 JavaScript 执行上下文与 this 绑定机制
|
16天前
|
自然语言处理 JavaScript 前端开发
如何在 JavaScript 中创建执行上下文
在JavaScript中,每当执行一段代码时,都会创建一个执行上下文。它首先进行变量、函数声明的创建和内存分配(即变量环境和词法环境的建立),接着进入代码执行阶段,处理具体逻辑。
|
29天前
|
JavaScript 测试技术 API
跟随通义灵码一步步升级vue2(js)项目到vue3版本
Vue 3 相较于 Vue 2 在性能、特性和开发体验上都有显著提升。本文介绍了如何利用通义灵码逐步将 Vue 2 项目升级到 Vue 3,包括备份项目、了解新特性、选择升级方式、升级依赖、迁移组件和全局 API、调整测试代码等步骤,并提供了注意事项和常见问题的解决方案。
|
1月前
|
JavaScript 前端开发 索引
JavaScript ES6及后续版本:新增的常用特性与亮点解析
JavaScript ES6及后续版本:新增的常用特性与亮点解析
30 4
|
13天前
|
JavaScript Linux iOS开发
详解如何实现自由切换Node.js版本
不同的项目中需要使用不同版本的 Node.js,有时旧项目需要旧版本,而新项目则可能依赖最新的 Node.js 版本
|
16天前
|
存储 自然语言处理 JavaScript
如何在 JavaScript 中创建执行上下文
在JavaScript中,作用域链是一套用于查找变量和函数的机制,由当前执行上下文的变量对象和所有外层执行上下文的变量对象组成。它包括全局作用域、函数作用域和块级作用域。作用域链的工作原理是从内向外逐层查找变量,直至全局作用域。闭包通过作用域链记住其词法作用域,即使在外部作用域之外执行也能访问内部变量。作用域链有助于变量隔离、模块化和数据隐藏,提高代码的可维护性和可读性。
|
2月前
vite.config.js中vite.defineConfig is not defined以及创建最新版本的vite项目
本文讨论了在配置Vite项目时遇到的`vite.defineConfig is not defined`错误,这通常是由于缺少必要的导入语句导致的。文章还涉及了如何创建最新版本的Vite项目以及如何处理`configEnv is not defined`的问题。
112 3
vite.config.js中vite.defineConfig is not defined以及创建最新版本的vite项目
|
2月前
|
移动开发 前端开发 JavaScript
JS配合canvas实现贪吃蛇小游戏_升级_丝滑版本_支持PC端和移动端
本文介绍了一个使用JavaScript和HTML5 Canvas API实现的贪吃蛇游戏的升级版本,该版本支持PC端和移动端,提供了丝滑的转向效果,并允许玩家通过键盘或触摸屏控制蛇的移动。代码中包含了详细的注释,解释了游戏逻辑、食物生成、得分机制以及如何响应不同的输入设备。
58 1
JS配合canvas实现贪吃蛇小游戏_升级_丝滑版本_支持PC端和移动端
|
2月前
|
JavaScript Linux 开发者
一个用于管理多个 Node.js 版本的安装和切换开源工具
【9月更文挑战第14天】nvm(Node Version Manager)是一个开源工具,用于便捷地管理多个 Node.js 版本。其特点包括:版本安装便捷,支持 LTS 和最新版本;版本切换简单,不影响开发流程;多平台支持,包括 Windows、macOS 和 Linux;社区活跃,持续更新。通过 nvm,开发者可以轻松安装、切换和管理不同项目的 Node.js 版本,提高开发效率。
|
1月前
|
JavaScript 算法 内存技术
如何降低node.js版本(nvm下载安装与使用)
如何降低node.js版本(nvm下载安装与使用)
下一篇
无影云桌面