「JavaScript深入」一文说明白JS的执行上下文与作用域

news/2024/9/25 20:33:04/

JavaScript深入 — 执行上下文与作用域

    • 上下文
    • 执行上下文
      • 生命周期
        • 创建阶段
        • 执行阶段
        • 回收阶段
    • 执行栈
    • 作用域链
    • 作用域
      • 词法作用域(静态作用域)


上下文

变量或函数的上下文决定了它们可以访问哪些数据,以及它们的行为。 每个上下文都有一个关联的变量对象,而这个上下文中定义的所有变量和函数都存在于这个对象上。

全局上下文是最外层的上下文,(在浏览器中,全局上下文就是我们常说的 window 对象,因此所有通过 var 定义的全局变量和函数都会成为 window 对象的属性和方法)上下文在其所有代码都执行完毕后会被销毁,包括它上面的变量和函数。

每个函数调用都有自己的上下文。当代码执行流入函数时,函数的上下文被推到一个上下文栈上。在函数执行完之后,上下文栈会弹出该函数的上下文,将控制权返回给之前的执行上下文。


执行上下文

简单的来说,执行上下文是一种对JavaScript代码执行环境的抽象概念,也就是说,只要有JavaScript代码运行,那么它就一定是运行在执行上下文中,变量或函数的上下文决定了它们可以访问哪些数据,以及它们的行为。

执行上下文的类型分为三种:

  • 全局执行上下文:只有一个,浏览器中的全局对象就是 window 对象,this 指向这个全局对象
  • 函数执行上下文:存在无数个,只有在函数被调用的时候才会被创建,每次调用函数都会创建一个新的执行上下文
  • Eval 函数执行上下文:指的是运行在 eval 函数中的代码,很少用而且不建议使用

🌰 简单举例如下:

在这里插入图片描述

紫色框住的部分为全局上下文,蓝色和橘色框起来的是不同的函数上下文。只有全局上下文(的变量)能被其他任何上下文访问

可以有任意多个函数上下文,每次调用函数创建一个新的上下文,会创建一个私有作用域,函数内部声明的任何变量都不能在当前函数作用域外部直接访问

生命周期

执行上下文的生命周期包括三个阶段:创建阶段->执行阶段->回收阶段

创建阶段

创建阶段即当函数被调用,但未执行任何内部代码之前

  • 确定 this 的值,也被称为 This Binding
  • 词法环境(LexicalEnvironment)组件被创建
  • 变量环境(VariableEnvironment)组件被创建
// 伪代码
ExecutionContext = {ThisBinding = <this value>,LexicalEnvironment = { ... },VariableEnvironment = { ... },
}

This Binding
this 的值是在执行时候才能确认,定义时不能确认

词法环境
词法环境有两个组成部分:

  • 全局环境:是一个没有外部环境的词法环境,其外部环境引用为 null,有一个全局对象,this 的值指向这个全局对象
  • 函数环境:用户在函数中定义的变量被存储在环境记录中,包含了 arguments 对象,外部环境的引用可以是全局环境,也可以是包含内部函数的外部函数环境
// 伪代码
GlobalExectionContext = {    // 全局执行上下文LexicalEnvironment: {      // 词法环境EnvironmentRecord: {     // 环境记录Type: "Object",        // 全局环境// 标识符绑定在这里 outer: <null>          // 对外部环境的引用}  }
}
FunctionExectionContext = { // 函数执行上下文LexicalEnvironment: {     // 词法环境EnvironmentRecord: {    // 环境记录Type: "Declarative",  // 函数环境// 标识符绑定在这里      outer: <Global or outer function environment reference>  // 对外部环境的引用}  }
}

变量环境
变量环境也是一个词法环境,因此它具有上面定义的词法环境的所有属性

在ES6中,词法环境和变量环境的区别在于前者用于存储函数声明和变量(letconst )绑定,而后者仅用于存储变量(var)绑定

执行阶段

在这阶段,执行变量赋值、代码执行

如果JavaScript引擎在源代码中声明的实际位置找不到变量的值,那么将为其分配undefined

回收阶段

执行上下文出栈等待虚拟机回收执行上下文


执行栈

执行栈,也叫调用栈,后进先出,用于存储在代码执行期间创建的所有执行上下文

在这里插入图片描述

当Javascript引擎开始执行你第一行脚本代码的时候,它就会创建一个全局执行上下文然后将它压到执行栈中

每当引擎碰到一个函数的时候,它就会创建一个函数执行上下文,然后将这个执行上下文压到执行栈中

引擎会执行位于执行栈栈顶的执行上下文(一般是函数执行上下文),当该函数执行结束后,对应的执行上下文就会被弹出,然后控制流程到达执行栈的下一个执行上下文

🌰 举个例子:

let a = 'Hello World!';
function first() {console.log('Inside first function');second();console.log('Again inside first function');
}
function second() {console.log('Inside second function');
}
first();
console.log('Inside Global Execution Context');

🫱 输出结果:

Inside first function
Inside second function
Again inside first function
Inside Global Execution Context

👇 执行栈中内容用图表示:

在这里插入图片描述


作用域链

上下文中的代码在执行时,会创建变量对象的一个作用域链。这个作用域链决定了各级上下文中的代码在访问变量和函数时的顺序。

执行上下文的角度来说: 作用域链是由当前执行上下文和所有外层执行上下文的变量对象组成的链式结构。当JavaScript代码在一个执行上下文中查找变量时,会先在当前执行上下文的变量对象中查找,如果没有找到,就会继续在外层执行上下文的变量对象中查找,直到找到该变量或者到达全局执行上下文。

如果上下文是函数,则其活动对象用作变量对象,活动对象最初只有一个定义变量:arguments

代码正在执行的上下文的变量对象始终位于作用域链的最前端,全局上下文的变量对象始终是作用域链的最后一个变量对象。

var color = "blue";function changeColor() {if(color === "blue"){color = "red";} else {color = "blue";}
}

👆 如上例子,函数 changeColor() 的作用域链包含两个对象:一个是它自己的变量(定义了 arguments 对象),另一个是全局上下文的变量对象。

在这里插入图片描述

上图矩形表示不同的上下文。内部上下文可以通过作用域链访问外部上下文中的一切。

上下文之间的连接是线性的,有序的。每个上下文都可以到上一级上下文中去搜索变量和函数。

在这里插入图片描述

函数执行完毕后,局部活动对象会被销毁,内存中就只剩下全局作用域。


作用域

作用域是一套规则,用于确定在何处以及如何查找变量(标识符)。——《你不知道的JavaScript》

词法作用域(静态作用域)

作用域共有两种工作模型。第一种是最为普遍的,被大多数编程语言所采用的词法作用域,意味着作用域是由书写代码时函数声明的位置来决定的编译时就基本能够知道全部标识符在哪里以及是如何声明的,从而能够在执行过程中对它们进行查找。

var value = 1;function foo() {console.log(value);
}
function bar() {var value = 2;foo();
}bar(); // 1
// 在函数定义的时候确定的作用value等于1 所以调用的时候也等于1 

另一种叫作动态作用域,最常见的就是Bash脚本。

#test.sh
name='lily';function getName() {echo name: $name;
}function getMyName() {local name='lucy';getName;
}getMyName;

如上代码,如果是静态作用域,函数的作用域在定义时就确定了,输出应为“lily”,而Bash是动态作用域,所以其实输出的是“lucy”。


http://www.ppmy.cn/news/1530439.html

相关文章

vuex和redux的区别

Vuex和Redux是两个流行的JavaScript状态管理库&#xff0c;它们各自有着独特的特点和适用场景。以下是它们之间的一些主要区别&#xff1a; 1. 设计初衷与生态系统 Vuex&#xff1a;专为Vue.js框架设计&#xff0c;与Vue.js的概念和语法紧密集成。因此&#xff0c;在Vue社区中…

macOS平台编译libidn2库给iOS及macOS用

1.克隆源码: git clone https://gitlab.com/libidn/libidn2.git --recursive 2.安装依赖库: pkg-config也要安装 3.启动bootstrap生成configure 配置成功 configure生成成功

linux 基础(一)mkdir、ls、vi、ifconfig

1、linux简介 linux是一个操作系统&#xff08;os: operating system&#xff09; 中国有没有自己的操作系统&#xff08;华为鸿蒙HarmonyOS&#xff0c;阿里龙蜥(Anolis) OS 8、百度DuerOS都有&#xff09; 计算机组的组成&#xff1a;硬件软件 硬件&#xff1a;运算器&am…

操作系统 | 学习笔记 | | 王道 | 5.3 磁盘和固态硬盘

5.3 磁盘和固态硬盘 5.3.1 磁盘 磁盘结构 磁盘&#xff1a;磁盘的表面由一些磁性物质组成&#xff0c;可以用这些磁性物质来记录二进制数据 磁道&#xff1a;磁盘的盘面被划分成一个个磁道。这样的一个“圈”就是一个磁道 扇区&#xff1a;一个磁道又被划分成一个个扇区&am…

828华为云征文|云服务器Flexus X实例|Ubunt部署Vue项目

概要 本章将深入阐述Vue项目在Ubuntu环境下&#xff0c;实现在华为云Flexus X云服务器上的部署过程&#xff0c;此次演示以Vue.js项目为核心华为云在已经到来的828 B2B企业节上&#xff0c;为Vue等前端项目的部署与运维提供强有力的支持。 Ubuntu部署Vue项目的影响&#xff1…

【C++】C++入门概念(一)

C关键字 C总计63个关键字&#xff0c;C语言32个关键字 ps&#xff1a;下面我们只是看一下C有多少关键字&#xff0c;不对关键字进行具体的讲解。后面我们学到以后再详细讨论。 命名空间 在C/C中&#xff0c;变量、函数和后面要学到的类都是大量存在的&#xff0c;这些变量、…

USB Micro-A、Micro-B 插头与 Micro-AB、Micro-B 插座,及其引脚定义

微型连接器配对 下表总结了每个插座所接受的插头&#xff1a; 从上表可知&#xff0c;Micro-B 插座只能配对 Micro-B 插头&#xff1b;而 Micro-AB 插座则可以配对 Micro-A 插头或 Micro-B 插头。 Micro-A 插头中的五个引脚的用法和接线分配在下表中进行了定义&#xff1a; C…

使用vite+react+ts+Ant Design开发后台管理项目(四)

前言 本文将引导开发者从零基础开始&#xff0c;运用vite、react、react-router、react-redux、Ant Design、less、tailwindcss、axios等前沿技术栈&#xff0c;构建一个高效、响应式的后台管理系统。通过详细的步骤和实践指导&#xff0c;文章旨在为开发者揭示如何利用这些技术…