JS 的 9 种作用域,你能说出几种?

news2025/1/16 14:02:47

作用域想必大家都知道,就是变量生效的范围,比如函数就会生成一个作用域,声明的变量只在函数内生效。

而这样的作用域一共有 9 种,其中几种绝大多数前端都说不出来。

下面我们就一起过一遍这 9 种作用域吧,看看你知道几种:

(为了保证准确性,所有的作用域类型都是通过调试所得)

Global 作用域

通过 var 声明一个变量,打个断点,可以看到 Scope 里有 Global 类型的作用域,也就是全局作用域,里面保存了变量 a:

在浏览器环境下,可以通过 a 访问全局变量,也可以通过 window.a 访问。

Local 作用域

声明个函数,在函数内声明一个变量,调用这个函数的时候,可以看到 Scope 里有 Local 类型的作用域,也就是本地作用域,里面保存了变量 b:

这两种作用域都很常见,没啥好说的。

Block 作用域

es6 加入了块语句,它也同样会生成作用域:

如图,会把里面声明的变量 a 放到 Block 作用域内,也就是块级作用域。

if、while、for 等语句都会生成 Block 作用域:

前几种作用域很常规,但下面这种作用域绝大部分前端就不知道了:

Script 作用域

这段代码大家觉得会生成什么作用域:

很多同学都会说,不是全局作用域么?

那这个现象你能解释么:

a、b、c 如果都是全局变量,那在浏览器里就可以通过 window.xx 来访问,但结果 window.a 和 window.b 都是 undefined,而直接访问 a、b 能拿到值。

看下现在的作用域就知道了:

你会发现 let、const 声明的全局变量被放到了 script 作用域,而 var 声明的变量被放到了 global 作用域。

这就是浏览器环境下用 let const 声明全局变量时的特殊作用域,script 作用域。可以直接访问这个全局变量,但是却不能通过 window.xx 访问。

所以你再看到这样的代码,就不奇怪了:

window.xxx = xxx; 

这个 xxx 肯定是通过 let、const 声明的全局变量,需要手动挂到 window 上。

那上面这个 script 作用域在 node 环境里有么?

我们用 node 调试下:

模块作用域

同样的代码,在 node 环境下就没有了 Script 作用域,但是多了一个 Local 作用域:

这个 Local 作用域还有 module、exports、require 等变量,这个叫做模块作用域。

这个作用域有些特殊,其实它也是函数作用域。为什么呢?后面会有解释。

说到特殊的作用域,其实还有一些:

Catch Block 作用域

Catch 语句也会生成一个特殊的作用域,Catch Block 作用域,特点是能访问错误对象:

在 node 里也是一样,只不过还有一层模块作用域:

有同学会问,那 finally 语句呢?

这个就没啥特殊的了,就是 Block 作用域:

类似的还有 With Block:

With Block 作用域

大家猜下这个 with 语句里的作用域是是啥:

想必你猜到了,with 语句里的作用域就是这个对象:

换成普通的对象更明显一些:

可以直接访问 witch 对象的值,就是因为形成了一个 With Block 作用域,当然,里面再声明的变量还是在 Block 作用域里。

Closure 作用域

闭包是 JS 的常见概念,它是一个函数返回另一个函数的形式,返回的函数引用了外层函数的变量,就会以闭包的形式保存下来。

比如这样:

function fun() {const a = 1;const b = 2;return function () {const c = 2;console.log(a, c);debugger;};
}

const f = fun();
f(); 

那闭包的变量怎么保存的呢?

通过 node 可以看到:

通过 Closure 作用域保存了变量 a 的值,这个 Closure 作用域就是闭包的核心。

那为啥只保存了 a 没保存 b、c 呢?

c 是返回的函数的作用域里的,不是外部作用域,而 b 则是没用到,所以 Closure 作用域里只保存了 a。

然后执行的时候就会恢复这个 Closure 作用域:

这样函数需要的外部变量都在 Closure 作用域里,啥也没丢,可以正常执行。

是不是很巧妙!

这就是闭包的核心。

当然,Closure 作用域也可以多层,比如这样:

function fun() {const a = 1;const b = 2;return function () {const c = 2;const d = 4;return function () {const e = 5;console.log(a, c, e);};};
}

const f = fun()();
f(); 

用到的外部变量分别在两个作用域里,那就会生成两个 Closure 作用域:

只留下用到的作用域的变量 a、c。

执行的时候就会恢复这两层闭包作用域:

这样函数需要的外部环境一点都不少。

理解了 Closure 作用域,就真正理解了闭包。

闭包里还有一种特殊情况,就是 eval:

上面的代码如果我改动一下,把打印语句变成 eval,会发生什么呢?

function fun() {const a = 1;const b = 2;return function () {const c = 2;const d = 4;return function () {const e = 5;eval("console.log(a, c, e);");};};
}

const f = fun()();
f(); 

有的同学会说,这不是一样么,都会形成闭包。

没错,都会形成闭包,但是保存的变量不一样了:

你会发现它把所有外部的作用域的变量都保存到了 Closure 作用域,包括模块作用域的变量。

为什么呢?

因为它根本不会去分析字符串呀,也没法分析,万一你这段 JS 是动态从服务端获取再 eval 的呢?

没法分析!

没法分析怎么保证代码执行不出错呢?

全部保存不就行了?

所以当返回的函数有 eval 的时候,JS 引擎就会形成特别大的 Closure,会把所有的变量都放到里面。

这样再执行 eval 的时候就不会出错了:

所有的变量都给你了,怎么可能出错呢?

但是这样明显性能不好,会占用更多的内存,所以闭包里尽量不要用 eval。

前面说模块作用域是特殊的函数作用域,为什么这么说呢?

这就与 node 模块的执行机制有关系了。

比如这样一段代码:

function func() {require;debugger;
}
func(); 

执行后发现形成了闭包:

而如果不访问模块作用域的变量,就没有这一层了:

我这明明没有闭包的代码呀!

这就与 node 模块的执行机制有关系了:

node 会把模块变为一个函数,它有 exports、require、module、__dirname、__filename 这五个参数,然后传入这五个参数来执行:

所以模块作用域就是个函数作用域而已!

模块里的函数引用模块作用域的变量,再执行,自然就形成了闭包。

Eval 作用域

最后一种特殊的作用域就是 eval 作用域了。

比如这样一段代码:

eval(`const a = 1;const b = 2;const c = 3;console.log(a,b,c);debugger;
`); 

执行之后是这样的:

可以看到有单独的 Eval 作用域,eval 的代码里声明的变量都在这个作用域里:

总结

JS 总共有 9 种作用域,我们通过调试的方式来分析了下:

  • Global 作用域: 全局作用域,在浏览器环境下就是 window,在 node 环境下是 global
  • Local 作用域:本地作用域,或者叫函数作用域
  • Block 作用域:块级作用域
  • Script 作用域:let、const 声明的全局变量会保存在 Script 作用域,这些变量可以直接访问,但却不能通过 window.xx 访问
  • 模块作用域:其实严格来说这也是函数作用域,因为 node 执行它的时候会包一层函数,算是比较特殊的函数作用域,有 module、exports、require 等变量
  • Catch Block 作用域:catch 语句的作用域可以访问错误对象
  • With Block 作用域:with 语句会把传入的对象的值放到单独的作用域里,这样 with 语句就可以直接访问了
  • Closure 作用域:函数返回函数的时候,会把用到的外部变量保存在 Closure 作用域里,这样再执行的时候该有的变量都有,这就是闭包。eval 的闭包比较特殊,会把所有变量都保存到 Closure 作用域
  • Eval 作用域:eval 代码声明的变量会保存在 Eval 作用域

上面这些都是调试得出的,是 JS 引擎执行代码时的真实作用域。

JavaScript 的 9 种作用域,你能说出几种?

最后

为大家准备了一个前端资料包。包含54本,2.57G的前端相关电子书,《前端面试宝典(附答案和解析)》,难点、重点知识视频教程(全套)。



有需要的小伙伴,可以点击下方卡片领取,无偿分享

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

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

相关文章

具有自主、多鳍和仿生机器人的鱼类三维游泳(2021)

具有自主、多鳍和仿生机器人的鱼类三维游泳(2021) 原文链接:https://iopscience.iop.org/article/10.1088/1748-3190/abd013 这是一篇仿生机器鱼的设计,该论文从鱼的仿生结构到具体的一部分电路设计都有非常详细的介绍,鱼的尺寸大小仅有手掌…

只有从根本上改变对于元宇宙的看法,才能将它的发展带入到一个全新阶段

经历了资本的狂热追捧之后,元宇宙开始进入到相对冷静的发展阶段里。在这样一个阶段,元宇宙不再被看成是一个万能的存在,不再被看成是一个无所不包的存在,而是变成了一个相对较为客观和理性的存在。看看Meta的表现,看看…

未处理的非法访问读异常(插入影像图代码)

本文迁移自本人网易博客,写于2013年1月5日,未处理的非法访问读异常(插入影像图代码) - lysygyy的日志 - 网易博客 (163.com)Acad::ErrorStatus CustomApplication::createAcDbRasterImageDef (AcDbObjectId & parObjectId, AC…

2023年入职/转行网络安全行业,该如何规划学习?

前言 前段时间,知名机构麦可思研究院发布了**《2022年中国本科生就业报告》**,其中详细列出近五年的本科绿牌专业,其中,信息安全位列第一。 网络安全前景 对于网络安全的发展与就业前景,想必无需我多言,作…

【Leetcode】699. 掉落的方块(困难)

一、题目 1、题目描述 在二维平面上的 x 轴上,放置着一些方块。 给你一个二维整数数组 positions ,其中 positions[i][lefti,sideLengthi]positions[i] [left_i, sideLength_i]positions[i][lefti​,sideLengthi​] 表示:第 i 个方块边长…

基于YOLOV5的手势识别系统源码及模型,训练得到能识别10种常用手势的模型

毕设系列-基于YOLOV5的手势识别系统 完整代码下载地址:基于YOLOV5的手势识别系统源码及模型 本期我们带来的内容是基于YOLOV5的手势识别系统,我们将会训练得到能识别10种常用手势的模型,废话不多说,还是先看效果。 配置环境 不熟…

ROS2 Launch文件编辑及运行

在第一版的 ROS 中launch 一般使用 xml 文件的格式进行编写和运行,但从 ROS2 中增添了 py 的 python 的可执行文件 作用 为了解决频繁的 ros2 run 命令,和多个节点之间的运行及关闭 特性 launch文件允许我们同时启动和配置多个包含 ROS 2 节点的可执行…

Java学习者看过来。。。这些优质项目千万别错过

程序员宝藏库:https://gitee.com/sharetech_lee/CS-Books-Store 这么主流的编程语言,如果去GitHub搜一下,会发现Java项目多如牛毛。 这就会带来很多困扰,假如有10万个项目,想从其中找到适合初学、进阶等不同阶段的项目…

【OpenFOAM】-olaFlow-算例7-波面自适应网格

算例路径: none 算例描述: 波面附近采用自适应网格划分 学习目标: 动网格设置和使用,dynamicFvMesh dynamicRefineFvMesh 的各参数含义 学习体会:    (1) 在结构附近的加密网格,自适应网格依然会对细网格…

weston 窗口管理 (1)

窗口管理 (1) 一、概述 在传统嵌入式场景下,通常只会运行一个UI程序,故相当于单窗口程序,无需桌面服务器的介入;在桌面系统下,对于每一个UI程序而言,它们的行为相比于嵌入式场景仍然没有发生改变,其对接的仍然是窗口,只不过在同一个时刻允许多个UI程序同时运行. 无论如何对于…

Git的标签:tag

目录 1. 查看标签 1.1 简单查看 1.2 匹配筛选标签 2. 创建标签 2.1 附注标签 2.2 轻量标签 2.3 代码提交之后打标签 2.4 提交标签 3. 删除标签 4. 检出标签 Git 可以给仓库历史中的某一个提交打上标签,以示重要。 比较有代表性的是人们会使用这个功能来标记…

thrift OOM 内存溢出

最近经常发生thrift服务半夜宕机的问题,虽然是测试环境,但是每天早上重启也很恶心。 经过很长时间的摸索,终于找到了原因。先说背景: 我们用的thrift版本是0.9.2,用做service的rpc框架,某一天开始&#x…

安全狗云原生安全产品入选《2022网络安全技术应用试点示范项目名单》

近日,工信部正式发布《2022网络安全技术应用试点示范项目名单》。作为国内云原生安全领导厂商,安全狗也凭借突出的产品能力,入选名单。 据悉,此次评选需层层通过单位申报、部门初审和推荐、专家评审、网上公示等多个环节。安全狗…

玉湖冷链黄铮洪出任广东省物流标准化技术委员会副主任

1月5日,广东省物流标准化技术委员会(第三届)成立大会召开,玉湖冷链执行董事黄铮洪出任副主任委员。 大会现场 根据2022年9月广东省市场监督管理局发布的通告,决定成立第三届广东省物流标准化技术委员会(以下简称「标准化委员会」)。此次大会进…

七、k8s Service详解

文章目录1 Service介绍1.1 userspace 模式1.2 iptables 模式1.3 ipvs 模式2 Service类型3 Service使用3.1 实验环境准备3.2 ClusterIP类型的Service3.3 Endpoint3.4 HeadLiness类型的Service3.5 NodePort类型的Service3.6 LoadBalancer类型的Service3.7 ExternalName类型的Serv…

树莓派3B摄像头的详细使用教程(拍照+录像+监控)

树莓派4B摄像头的详细使用教程(拍照录像监控) 本篇博文将介绍树莓派摄像头是如何在树莓派开发板上从安装到使用的,博主过程中参考了许多帖子,现将整理的比较全面的过程分享出来,供大家参考使用。 排线连接 硬件连接时…

【阶段二】Python数据分析数据可视化工具使用02篇:条形图与雷达图

本篇的思维导图: 条形图 条形图与柱形图类似,几乎可以表达相同多的数据信息。条形图的柱形变为横向,从而导致与柱形图相比,条形图更加强调项目之间的大小对比。尤其在项目名称较长以及数量较多时,采用条形图可视化数据会更加美观、清晰。 代码 # 导入需要的包imp…

java学习day70(乐友商城)授权中心

1.无状态登录原理 1.1.什么是有状态? 有状态服务,即服务端需要记录每次会话的客户端信息,从而识别客户端身份,根据用户身份进行请求的处理,典型的设计如tomcat中的session。 例如登录:用户登录后&#x…

cubeIDE开发, stm32的C库应用分析

一、stm32的C库 cubeIDE针对STM32芯片开发,提供个了两大库,HLA库和C库(集成GNU Tools for STM32工具链时提供,该工具链同样是意法半导体提供,可在http:// www.st.com单独下载),前者帮助开发这简…

P1055 [NOIP2008 普及组] ISBN 号码————C++

文章目录题目[\[NOIP2008 普及组\] ISBN 号码](https://www.luogu.com.cn/problem/P1055)题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1样例 #2样例输入 #2样例输出 #2提示解题思路1Code运行结果解题思路2Code运行结果题目 [NOIP2008 普及组] ISBN 号码 题目描述 …