《七》JavaScript 中的作用域、作用域链、执行上下文、执行上下文栈

news2024/12/28 21:17:30

JS 引擎会在执行所有代码之前,先在堆内存中创建一个全局对象(Global Object、GO),包含 String、Math、Date、parseInt() 等属性和方法。所有作用域都可以访问这个全局对象。

在浏览器中 Global Object 就是 Window 对象。

执行上下文(执行环境、Execution Context):

执行上下文是当前代码的执行环境。每当 JS 代码在运行的时候,它都是在执行上下文中运行。

执行上下文的类型:

JS 中有三种执行上下文的类型:

  1. 全局执行上下文:全局执行上下文是最外围的执行上下文。在 web 浏览器中,全局执行上下文被认为是 window 对象,因此所有的全局变量和全局函数都是作为 window 对象的属性和方法创建的。一个程序中只会有一个全局执行上下文。

    在执行全局的代码前,JS 引擎会创建一个全局执行上下文;程序执行完毕或者网页被关闭后,全局执行上下文环境被销毁。

  2. 函数执行上下文:每个函数都有自己的执行上下文。函数执行上下文可以有无数个。

    每当一个函数被调用时, 都会为该函数创建一个新的函数执行上下文;当函数执行完毕后,该函数执行上下文被销毁。

  3. eval 函数执行上下文:eval 函数内部的代码也有属于它自己的执行上下文,很少用到。

执行上下文的生命周期:

执行上下文有创建、执行、回收三个生命周期。

创建阶段:

创建阶段会做三件事:创建变量对象、创建作用域链、确定 this 的指向。

因此,每个执行上下文,都有三个重要的属性:变量对象、作用域链、this。

  1. 创建变量对象。

    变量对象(Variable object,VO):每个执行上下文都会关联一个变量对象,当前执行上下文中定义的所有变量声明、函数声明、函数的形参都会被添加到这个对象中。这就是函数声明提升和变量提升的原因
    变量对象包括:

    1. 变量声明:由变量的名称和其对应的值组成变量对象的一个属性,此时其对应的值是 undefined。如果变量的名称和已经声明的函数的名称或者函数的形参相同,变量声明不会干扰到这些已经存在的属性。
    2. 函数声明:由函数的名称和其对应的值组成变量对象的一个属性,此时其对应的值是函数对象本身,在执行上下文的创建阶段,函数就会被创建出来。如果变量对象已经存在相同名称的属性,则完全替换这个属性。
    3. 函数的形参(如果是函数执行上下文的话):由函数形参的名称和其对应的值组成变量对象的一个属性。如果没有实参的话,属性值为 ubdefined。

    活动对象(activation object, AO):活动对象其实就是被激活的变量对象,只有到当进入一个执行上下文中,这个执行上下文的变量对象才会被激活,所以才叫 activation object。
    变量对象是规范上的或者说是引擎实现上的,无法在 JavaScript 环境中访问到;只有活动对象上的各种属性才能被访问。

    function foo(a) {
      console.log(b)
      console.log(c)
      var b = 2
      function c() {}
      var d = function() {}
      b = 3
    }
    foo(1)
    
    // 当进入代码还没执行时,这时候的 AO:
    AO = {
        arguments: {
            0: 1,
            length: 1
        },
        a: 1,
        b: undefined,
        c: reference to function c(){},
        d: undefined
    }
    
  2. 创建作用域链:当执行函数,进入函数执行上下文,创建 AO 后,就会将活动对象添加到作用域链的最前端。

    函数作用域是在函数定义的时候确定的,这是因为函数有一个内部属性 [[scope]],当函数定义的时候,就会保存所有父变量对象到其中,[[scope]] 就是所有父变量对象的层级链。当执行函数,进入函数执行上下文,创建 AO 后,就会将活动对象添加到作用域链的最前端。
    当代码在一个环境中执行时,会创建变量对象的一个作用域链。作用域链的前端,始终都是当前执行的代码所在环境的变量对象,作用域链中的下一个变量对象来自包含环境,再下一个变量对象则来自下一个包含环境,一直延续到全局执行环境,全局执行环境的变量对象始终都是作用域链中的最后一个对象。
    当查找变量时,会先从当前作用域的执行上下文的变量对象中查找,如果没有找到,就会从父级作用域执行上下文的变量对象中查找,一直找到全局作用域的执行上下文的变量对象。
    作用域链的用途,是保证对执行环境有权访问的所有变量和函数的有序访问。

    function out() {
      console.log(out)
      function inner() {
        console.log(inner)
      }
    }
    out()
    

    请添加图片描述

  3. 确定 this 的指向。

执行阶段:

从上到下依次执行代码。遇到变量声明的话,为变量赋值;遇到函数声明的话,由于函数在执行上下文创建阶段已经被创建,此时会直接跳过。

// 当代码执行后,这时候的 AO:
AO = {
    arguments: {
        0: 1,
        length: 1
    },
    a: 1,
    b: 3,
    c: reference to function c(){},
    d: reference to FunctionExpression "d"
}

// 因此,代码执行顺序是这样的:导致了变量提升和函数提升
function foo(a) {
	var b
	 unction c() {}
	var d
	console.log(b)
	console.log(c)
	b = 2
	function c() {}
	d = function() {}
	b = 3
}
回收阶段:

执行上下文出栈,被垃圾回收机制进行回收。

执行上下文栈(Execution Context Stack、ECS):

JS 引擎内部有一个执行上下文栈,它是用于执行代码的调用栈,被用来存储和管理代码运行时创建的所有执行上下文,拥有 LIFO(后进先出)的数据结构。

当 JS 引擎执行 JS 脚本时,它首先会创建一个全局的执行上下文并且压入执行上下文栈;每当 JS 引擎遇到一个函数调用,它会为该函数创建一个新的执行上下文并压入执行上下文栈的顶部;当该函数执行结束时,执行上下文从执行上下文栈中弹出,控制流程到达执行上下文栈的下一个执行上下文。

全局执行上下文总是在栈的底部;当前运行的总是栈顶的那个执行上下文。

let str = 'javascript'

function foo() {
    console.log('foo')
    bar()
}
function bar() {
    console.log('bar')
}
foo()
  1. 当上述代码在浏览器中运行时,JS 引擎首先会创建一个全局执行上下文并把它压入执行上下文栈。
  2. 当遇到 foo() 函数调用时, JS 引擎创建了一个 foo 函数的执行上下文并把它压入到执行上下文栈的顶部。
  3. 当从 foo() 函数内部调用 bar() 函数时,JS 引擎创建了一个 bar 函数的执行上下文并把它压入到执行上下文栈的顶部。
  4. bar() 函数执行完毕,它的执行上下文会从执行上下栈中弹出,控制流程到达下一个执行上下文,即 foo() 函数的执行上下文。
  5. foo() 函数执行完毕,它的执行上下文从执行上下栈中弹出,控制流程到达全局执行上下文。
  6. 一旦所有代码执行完成,JS 引擎就从执行上下文栈中移除全局执行上下文。

在这里插入图片描述

作用域 Scope:

作用域就是一段代码所在的区域。分为全局作用域、函数作用域和块级作用域(ES6 中新增)。作用域的作用是隔离变量,不同作用域下同名变量不会有冲突。

JS 采用词法作用域,也就是静态作用域。函数的作用域在函数定义的时候就决定了。

与词法作用域相对的是动态作用域,函数的作用域是在函数调用的时候才决定的。

ES6 之前,if 语句、for 语句等的大括号没有封闭作用域的功能,都是全局作用域。

if (true) {
  var box=’blue’
}
console.log(box) // blue
var a = 1
function out(){
    var a = 2
    inner()
}
function inner(){
    console.log(a)
}
out()  // 1

作用域链:

作用域链:由多个执行上下文的变量对象构成的链表就叫做作用域链,它的方向是从下而上的。查找变量时就是沿着作用域链来查找的。

查找一个变量的规则:在当前作用域下的执行上下文的变量对象中查找该变量,如果有直接返回,否则再在上一级作用域的执行上下文的变量对象中查找,以此类推,直到全局作用域,如果还找不到抛出异常。

内部环境可以通过作用域链访问所有的外部环境,但是外部环境不能访问内部环境中的任何变量和函数。

var a  = 1
function fn1() {
	var b = 2
	function fn2() {
		var c = 3
		console.log(c) // 3
		console.log(b) // 2
		console.log(a) // 1
		console.log(d) // 报错
	}
	fn2()
}
fn1()
延长作用域链:

有些语句可以在作用域链的前端临时增加一个变量对象,作用域链就会得到加长,该变量对象会在代码执行后被移出。对 with 语句来说,会将指定的对象添加到作用域链的最前端;对 catch 语句来说,被抛出的错误对象会创建一个新的变量对象加到作用域的最前端。

作用域与执行上下文的联系与区别:

上下文栈的数量是 n + 1:n 是调用几次函数, 1 是全局执行上下文。
作用域的数量也是 n + 1:n 是定义几次函数,1 是全局作用域。

var x = 10
function fn1() {
  console.log(x)
}
function fn2(f) {
  var x = 20
  f() // 打印输出 10。有三个作用域:全局作用域、fn1 函数作用域、 fn2 函数作用域,作用域在函数定义时就已经确定好不会再变了。因此 fn1() 的上级作用域是全局作用域
}
fn2(fn1)

联系:执行上下文是从属于所在的作用域。

区别:

  1. 作用域是静态的,在编写代码的时候就确定了;执行上下文是动态的,在调用执行的时候才确定。

    JS 代码的整个执行过程,分为两个阶段,代码编译阶段与代码执行阶段。
    编译阶段由编译器完成,将代码翻译成可执行代码,这个阶段作用域规则会确定。
    执行阶段由引擎完成,主要任务是执行可执行代码,执行上下文在这个阶段创建。

  2. 全局作用域之外,每个函数都会创建自己的作用域,只要函数定义好了就一直存在,且不会再变化;全局执行上下文是在全局作用域确定之后、JS 代码马上执行之前创建的,函数执行上下文是在调用函数时、函数体代码执行之前创建的。

当编写代码的时候,确定全局作用域和函数作用域 —> 当执行代码的时候,动态创建和销毁全局执行上下文和函数执行上下文。
当创建执行上下文的时候,会创建一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中 --> 会创建变量对象的一个作用域链 —> 当查找变量时,会先从当前作用域的执行上下文的变量对象中查找,如果没有找到,就会从父级作用域执行上下文的变量对象中查找,一直找到全局作用域的执行上下文的变量对象。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

不用机器学习不用大数据,给你讲通ChatGPT的深层原理

ChatGPT现在看来已经异常火爆了,很多人已经熟知,并且开始练习使用或者开始利用他开始实践了。但仍然有很多人在观望,在疑惑,今天狗哥不用那些高端大气的机器学习亦或是大数据还给你讲通ChatGPT深层到底是个啥逻辑。 目录 1. 聊家…

CV——dy83 接昨天的论文中DAM模块:压缩-激励的宽残差网络在图像分类中的应用

压缩-激励的宽残差网络在图像分类中的应用(ICIP 2019)1. INTRODUCTION2. PROPOSED METHODS2.1 总体框架2.2 通道的重要性3. EXPERIMENTS3.1 Datasets3.2 训练和测试的设置3.3 分类结果及分析4. CONCLUSIONSQUEEZE-AND-EXCITATION WIDE RESIDUAL NETWORKS…

CSS 选择器以及CSS常用属性

目录 🐇今日良言:可以不光芒万丈,但不要停止发光 🐯一、写CSS的三种方法 🐯二、CSS选择器的常见用法 🐯三、CSS常用属性 🐇今日良言:可以不光芒万丈,但不要停止发光 🐯一、写CSS的三种方法 CSS的基本语…

目标检测开源数据集汇总

导 读本文汇总了一些开源目标检测类的数据集,附下载链接。多显著性对象数据集数据集链接:http://m6z.cn/5AsmXB本数据集共有 1224 张图像来自四个公共图像数据集:COCO、VOC07、ImageNet 和 SUN。Amazon Mechanic Turk 工作人员将每个图像标记…

Firebase入门使用 01

官网 firebase.google.com 解决问题 firebase 帮助解决 数据库 和 API之间的问题 这样我们就可以 集中精力开创应用。 快速上手样例指南 https://github.com/firebase 提供的服务 其中80%用不到,下面是一些我们可以用到的服务。 Authentication:用户认证管理…

Qt安装与使用经验分享;无.pro文件;无QTextCodec file;Qt小试;界面居中;无缝;更换Qt图标;更换Qt标题。

1、切换安装下载源 《Qt安装教程》先推荐一篇安装文章:《Qt安装教程》 Qt 5.15 之后已经不提供离线安装包了,就是那个 3.7G 的 exe 安装包。请看官方说明,所以只能用在线安装包。 1,下载在线安装包 QT 在线安装包链接&#xff…

基于WSL2和Clion搭建Win下C开发环境

系列文章目录 一、基于WSL2和Clion搭建Win下C开发环境 二、make、makeFile、CMake、CMakeLists的使用 三、全面、详细、通俗易懂的C语言语法和标准库 文章目录系列文章目录前言WSL2安装WSL常用命令VSCode连接WSLroot密码以systemd启动配置sshClion结语前言 Win下C语言开发环境…

zabbix-API对接实录:关键基础设施数据清洗和封装函数(php数组函数、数据清洗、数据结构化)

系列文章目录 Zabbix监控系统PHP-API开发测试实录Zabbix监控系统开发(2):JSON多维数组筛选字段是否包含字符串的解决方案Zabbix物联网可视化开发文档 文章目录系列文章目录前言一、zabbix-API数据爬虫二、主机ID封装接口1.封装API接口2.数据处理封装函数三、组ID封装接口1.格式…

汽车 Automotive > T-BOX GNSS高精定位测试相关知识

参考:https://en.wikipedia.org/wiki/Global_Positioning_SystemGPS和GNSS的关系GPS(Global Positioning System),全球定位系统是美国军民两用的导航定位卫星系统,GPS包含双频信号,频点L1、L2和L5GNSS&…

RecyclerView ViewType二级

实现效果描述: 1、点击recyclerview中item,列表下方出现其他样式的item,作为子item,如下所示 所需要的java文件和xml文件有: 1、创建FoldAdapteradapter, 在FoldAdapter中,定义两种不同的类型&#xff…

Allegro如何将Waived掉的DRC显示或隐藏操作指导

Allegro如何将Waived掉的DRC显示或隐藏操作指导 在用Allegro做PCB设计的时候,如果遇到正常的DRC,可以用Waive的命令将DRC不显示,如下图 当DRC被Waive掉的时候,如何将DRC再次显示出来。类似下图效果 具体操作如下 点击Display

linux下strace的使用

strace是一款用于跟踪Linux系统调用和信号的工具,可以帮助开发者排除程序运行时的问题。 具体来说,strace可以跟踪一个程序执行时所涉及到的系统调用,包括读写文件、网络通信、进程管理、内存管理等操作,通过分析程序运行过程中发…

JavaWeb--JSP案例

JSP案例8 案例8.1 环境准备8.1.1 创建工程8.1.2 创建包8.1.3 创建表8.1.4 创建实体类8.1.5 准备mybatis环境8.2 查询所有8.2.1 编写BrandMapper8.2.2 编写工具类8.2.3 编写BrandService8.2.4 编写Servlet8.2.5 编写brand.jsp页面8.2.6 测试8.3 添加8.3.1 编写BrandMapper方法8.…

ARM uboot 的移植0-从三星官方 uboot 开始移植的准备工作

一、移植前的准备工作 1、三星移植过的uboot源代码准备 (1) 三星对于 S5PV210 的官方开发板为 SMDKV210,对应的移植过的 uboot 是:三星官方为210移植过的uboot和kernel/android_uboot_smdkv210.tar.bz2。 (2) 这个源代码网上是下载不到的,…

Leetcode.2397 被列覆盖的最多行数

题目链接 Leetcode.2397 被列覆盖的最多行数 Rating : 1719 题目描述 给你一个下标从 0 开始的 m x n二进制矩阵 mat和一个整数 cols,表示你需要选出的列数。 如果一行中,所有的 1 都被你选中的列所覆盖,那么我们称这一行 被覆盖…

RabbitMQ的使用以及整合到SpringBoot中

RabbitMQ的使用以及整合到SpringBoot中 一、比较: (1)、传统请求服务器: (2)、通过MQ去操作数据库: 通过MQ去操作数据库,从而达到削峰的效果; 问题现象: (1)、海量数据; (2)、高并发&#…

Python如何获取弹幕?给你介绍两种方式

前言 弹幕可以给观众一种“实时互动”的错觉,虽然不同弹幕的发送时间有所区别,但是其只会在视频中特定的一个时间点出现,因此在相同时刻发送的弹幕基本上也具有相同的主题,在参与评论时就会有与其他观众同时评论的错觉。 在国内…

【SQLAlchemy】第二篇——连接失效及连接池

一、背景 为了节约资源,MySQL会对建立的连接进行监控,当某些连接处于不活跃状态的时间超过一个阈值时,则关闭它们。 用户可以执行show variables like %wait_timeout%;来查看这个阈值: 可以看到,在默认的情况下&…

Multi-modal Graph Contrastive Learning for Micro-video Recommendation

模型总览如下: 解决问题:同种重要性对待每种模态,可能使得得到的特征表示次优,例如过度强调学习到的表示中的特定模态。以MMGCN为例,下图为MMGCN模型总览。 如上图所示MMGCN在每种模态上构建用户-物品二部图&#xff0…

【云原生】Gateway网关选型

网关一般分为流量网关和业务网关,流量网关负责接入所有的流量,并分发给不同的子系统,那在具体的业务接入之前,还有一层业务网关。流量网关提供全局性的、与后端业务应用无关的策略,例如 HTTPS证书卸载、Web防火墙、全局…