【前端】特殊案例分析深入理解 JavaScript 中的词法作用域

文章目录
- 💯前言
- 💯案例代码
- 💯词法作用域(Lexical Scope)与静态作用域
- 什么是词法作用域?
- 代码执行的详细分析
- 💯函数定义与调用的区别
- 💯动态作用域的对比
- 💯小结与拓展
- 💯小结

💯前言
- JavaScript 作为一种同时广泛应用于前端和后端的编程语言,其核心机制之一便是 作用域。这种机制对变量和函数的 可见性、访问权限 以及 生命周期 起着至关重要的作用,直接影响了代码的行为和执行逻辑。在本文中,我们将深入探讨 JavaScript 的词法作用域 及其相关的 作用域链机制,以此揭示代码执行过程中隐藏的复杂逻辑。通过一个简单而深刻的代码实例,我们将逐步解开 JavaScript 作用域的奥秘,以便更好地理解其背后的设计理念。无论是 初学者 还是有经验的 开发者,对这些概念的掌握都至关重要,是写出 高质量 JavaScript 代码 的基础。
JavaScript
💯案例代码
首先,让我们从一个简短的代码示例入手,这段代码看似简单,却蕴含了 JavaScript 中关于作用域的一些深刻原理。
var x = 10;function f1() {console.log(x); // 输出为 10
}function fn2() {var x = 20;f1(); // 调用 f1
}fn2(); // 调用 fn2

当 fn2() 被调用时,f1() 函数也随之执行。然而,f1() 的输出是 10,而不是 20。这个现象正是 JavaScript 词法作用域的核心体现。接下来,我们将深入分析其背后的原因,并阐述 JavaScript 中关于作用域的独特之处。
💯词法作用域(Lexical Scope)与静态作用域
JavaScript 使用的是 词法作用域,这一概念也被称为 静态作用域。词法作用域的本质在于:函数所能访问的变量,取决于函数在代码中定义的位置,而非函数在运行时调用的位置。这一规则使得 JavaScript 的作用域在代码的编写阶段就已经确定,而不依赖于代码运行时的调用环境。
![]()
什么是词法作用域?
![]()
词法作用域的定义是:函数在定义时就决定了它可以访问哪些变量,这种访问环境在函数创建的那一刻就被固定下来。换句话说,函数的 作用域链(Scope Chain) 在函数定义时已经锁定,不会因函数被调用的环境而发生改变。JavaScript 因此是静态作用域语言,其行为完全依赖于代码的书写和定义位置。
在上述代码中,函数 f1 被定义在全局作用域中,这意味着 f1 的作用域链中包含全局变量。因此,当 f1 尝试访问变量 x 时,它会从它的作用域链开始查找,因为 f1 的定义是在全局环境中,因此它首先会查找全局作用域中的变量 x,最终找到 x = 10,所以输出为 10。
代码执行的详细分析
![]()
接下来,我们逐步剖析代码的执行过程:
-
全局上下文的创建:
- 在全局环境中,变量
x被声明并赋值为10。这意味着全局作用域中存在一个变量x,其值为10,供整个程序访问。
- 在全局环境中,变量
-
f1函数的定义:f1函数在全局作用域中被定义。在函数f1被创建的那一刻,JavaScript 引擎已经确定了f1的作用域链。也就是说,f1拥有对全局作用域中所有变量的访问权限,包括变量x。
-
fn2函数的定义和调用:- 函数
fn2被定义,并且在其内部定义了一个局部变量x,其值为20。随后,fn2调用了f1。 - 当
fn2被调用时,局部变量x被赋值为20,此时fn2中的x和全局的x是两个完全独立的变量,处于不同的作用域中。 - 尽管在
fn2内部调用了f1,但是f1的作用域链是在其定义时确定的,它只包含了它自己和全局变量。因此,当f1试图访问变量x时,它只能访问全局的x,输出结果为10。

- 函数
💯函数定义与调用的区别
理解 JavaScript 词法作用域的关键在于区分 函数的定义和调用。函数的定义位置决定了它的作用域链,而这种作用域链在函数执行时并不会因调用位置的不同而改变。
在前述代码中,函数 f1 是在全局作用域中定义的,因此它的作用域链包含了全局变量。当 f1 被调用时,无论它是从全局环境还是其他函数内部调用,它始终按照定义时的作用域链来解析变量。因此,即使 fn2 中定义了局部变量 x = 20,f1 依然无法访问到 fn2 中的 x,因为 f1 的作用域链在其定义时已经固定,不包括 fn2 的局部变量。
💯动态作用域的对比
![]()
为了更好地理解词法作用域,我们不妨对比一下 动态作用域 的概念。动态作用域与词法作用域的最大区别在于,动态作用域基于函数的调用位置来决定变量的可见性。
若 JavaScript 使用的是动态作用域,那么在 fn2 中调用 f1 时,f1 会首先查找调用环境中的变量 x,也就是 fn2 中的 x,其值为 20。然而,JavaScript 并不是这样工作的。JavaScript 采用词法作用域,作用域链在函数定义时已经确定,而不会因调用位置的不同而改变。
动态作用域 意味着变量解析基于函数的调用栈,而不是其定义位置。换言之,在动态作用域语言中,函数在调用时会根据当前调用的上下文来决定变量的绑定关系。这与 JavaScript 的词法作用域完全不同,因为 JavaScript 在函数定义时就已确定了其作用域链。理解这种差异对于更好地掌握 JavaScript 的行为和作用域管理至关重要。
💯小结与拓展
![]()
通过这段代码,我们能够更深入地理解 JavaScript 中的一个核心概念:词法作用域。函数的作用域链在函数被定义时就已经确定,而不是等到函数被调用时才动态地重新决定。这意味着函数始终根据它在代码中定义的位置来解析变量,而不是依据它在运行时的调用位置。这一特性使得 JavaScript 的作用域模型更为直观,但也可能导致在嵌套调用时出现令人困惑的行为。
为了更好地掌握这一概念,我们可以记住以下几点:
- 函数的作用域链在定义时确定,而不是在调用时确定。
- 全局变量在任何函数中都可以被访问,除非函数中存在同名的局部变量。同名的局部变量只会在其函数内部遮蔽全局变量。
- 局部变量的作用域仅限于定义它们的函数内部,在函数外部无法访问,这有助于避免变量的意外冲突。
- 如果需要在函数调用时访问不同的变量,可以通过 闭包 来实现,但词法作用域的规则仍然适用。
- 闭包 是 JavaScript 中的一个重要概念,允许函数携带其定义时的作用域。这使得函数即使在离开定义环境后,仍然可以访问该作用域中的变量,这是理解 JavaScript 词法作用域的重要一环。
闭包 的强大在于它允许创建私有变量、记住函数的执行状态以及实现函数工厂等功能。在 JavaScript 中,闭包的应用极为广泛,尤其是在处理异步操作、回调函数 等情境时,闭包的特性能够确保函数可以访问定义时的作用域。
理解 JavaScript 的 词法作用域 对于编写健壮、可维护的代码至关重要。尤其是在处理嵌套函数、回调函数或模块化代码时,深入掌握作用域规则可以帮助我们避免许多潜在的错误。例如,在编写回调函数时,我们可能在无意识中引用了全局变量或外部函数的变量。如果对 词法作用域 的理解不够深入,这些问题往往难以察觉。
此外,立即执行函数表达式(IIFE) 是一种常用技术,用于在 JavaScript 中创建局部作用域,防止变量污染全局环境。通过 IIFE,我们可以在函数内部创建私有变量,从而避免它们泄漏到全局作用域中。这对代码的维护性和可读性具有非常重要的意义。IIFE 充分利用了 JavaScript 的词法作用域机制,为开发者提供了一个简洁的解决方案来隔离作用域。
另外,ES6 引入的 let 和 const 关键字 进一步增强了对作用域的控制。这些关键字使得变量具有块级作用域,避免了传统 var 所带来的变量提升和全局污染问题。块级作用域使得代码逻辑更加明确,特别是在循环和条件判断中使用时,可以显著减少潜在的作用域问题。
💯小结

希望本文能够帮助你深入理解 JavaScript 中的作用域机制。如果在编写代码时遇到关于作用域的困惑,不妨回顾这段代码,思考定义位置与调用位置之间的区别如何影响变量的可见性。这将有助于你大大提高代码的质量和调试的效率。同时,通过多练习各种作用域相关的问题,并理解 JavaScript 如何解析变量,你将在面对复杂代码时更加得心应手,对 JavaScript 的特性有更深刻的理解。

相关文章:
【前端】特殊案例分析深入理解 JavaScript 中的词法作用域
博客主页: [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: 前端 文章目录 💯前言💯案例代码💯词法作用域(Lexical Scope)与静态作用域什么是词法作用域?代码执行的详细分析 💯函数定义与调用的…...
Jmeter进阶篇(29)AI+性能测试领域场景落地
🏝️关于我:我是綦枫。一个顺手写写代码的音乐制作人。 前言 随着2022年GPT3.5的问世,我们的社会已经进入了AI时代,这是一个全新的风口,也会迎来全新的挑战和机遇。如果能抓住新时代的风口,你将会在进步的路上越走越快。今天让我们来一起探究一下,在软件性能测试领域,…...
理解和应用 Python Requests 库中的 .json() 方法:详细解析与示例
理解和应用 Python Requests 库中的 .json() 方法:详细解析与示例 在使用 Python 的 requests 库进行网络请求时,.json() 方法是一种非常实用的功能,用于将从 API 获取的 JSON 格式的字符串响应转换为 Python 可操作的字典或列表。这一功能的…...
docker 运行my-redis命令
CREATE TABLE orders ( order_id bigint NOT NULL COMMENT "订单ID", dt date NOT NULL COMMENT "日期", merchant_id int NOT NULL COMMENT "商家ID", user_id int NOT NULL COMMENT "用户ID", good_id int NOT NULL COMMENT "商…...
cloudstack概要及单节点安装部署
概要 Apache CloudStack 是一个开源的云计算管理平台,用于管理和部署大规模的虚拟化环境,支持 IaaS(基础设施即服务)模型。它广泛应用于私有云、公共云和混合云场景。 核心功能 多租户支持 提供隔离的虚拟网络、计算资源和存储资…...
Android Gradle 相关
JDK环境配置: 1、Gradle运行时的JDK,即Gradle需要用到的JDK,配置如下: 如需修改现有项目的 Gradle JDK 配置,请依次点击 File(或者 macOS 上的 Android Studio)> Settings > Build, Exe…...
SpringMVC:入门案例
从此开始,我们步入SpringMVC的学习。 SpringMVC是一种基于Java实现MVC模型的轻量级Web框架 先来看一下web程序是如何工作的: 因为是异步调用,所以后端不需要返回view视图,将其去除前端如果通过异步调用的方式进行交互࿰…...
LuaForWindows_v5.1.5-52.exe
Releases rjpcomputing/luaforwindows GitHub #lua C:\Users\Administrator\Desktop\test.lua print("Hello lua!") print("ZengWenFeng 13805029595")...
密码学实验工具--Cryptool2
一、 Cryptool2的下载与安装 请参考我的另一篇笔记 二、 Caesar密码 2.1 Caesar密码加解密 1. 在Starcenter中直接搜索caesar的模板。 2. 打开caesar Cipher的模板后,工作区上面已经有了输入框,密钥框,输出框 输入框:要加密…...
量化交易系统开发-实时行情自动化交易-8.1.TradingView平台
19年创业做过一年的量化交易但没有成功,作为交易系统的开发人员积累了一些经验,最近想重新研究交易系统,一边整理一边写出来一些思考供大家参考,也希望跟做量化的朋友有更多的交流和合作。 接下来会对于TradingView平台介绍。 T…...
Vue2 常见知识点(二)
使用简单的代码逻辑,理一理实现逻辑 为了方便理解,案例中,没有使用虚拟dom和抽象语法树,是通过直接操作dom来实现的 1.模板语法 先看一个简单的实现: this.compile( this.$el ); 执行模板编译,如果是文本…...
SAP-ABAP开发-第二代增强示例
CUSTOMER EXIT 以VA01为例 目录 一、查找出口 二、出口对象 三、销售订单的增强 一、查找出口 ①查找事务代码的主程序 ②搜索CALL CUSTOMER-FUNCTION SE37下查看函数 函数名称命名规则:EXIT_<程序名>_<序号> ③使用函数查找:MODX_FU…...
UDP 协议与端口绑定行为解析:理解 IP 地址和端口的绑定规则
UDP 协议与端口绑定行为解析:理解 IP 地址和端口的绑定规则 1. UDP 协议与端口绑定基础2. UDP 端口绑定行为与示例3. 关键结论:占有权与消息接收权4. 异常现象:多个程序绑定 0.0.0.0:80805. 端口共享与操作系统的行为差异6. 实践建议与最佳实践7. 总结在网络通信中,UDP(用…...
【Vue3】【Naive UI】<n-message>标签
【Vue3】【Naive UI】标签 content (String | VNode) 【VUE3】【Naive UI】<NCard> 标签 【VUE3】【Naive UI】<n-button> 标签 【VUE3】【Naive UI】<a> 标签 【VUE3】【Naive UI】<NDropdown&…...
C++ 变量和常量:开启程序构建之门的关键锁钥与永恒灯塔
目录 一、变量 1.1 变量的创建 1.2 变量的初始化 1.3 变量的分类 1.4 变量的初始化 二、常量 2.1 字面常量 2.2 #define定义常量 2.3 const 定义常量 一、变量 1.1 变量的创建 data_type name; | | | | 数据类型 变量名 ------------- int age; //整型变量 char ch; …...
Linux部分实用操作
目录 1、快捷键 2、软件安装 3、systemctl 4、ln命令创建软连接 5、IP地址 6、主机名 7、域名解析 8、网络传输 ping wget curl命令 9、端口 10、进程 11、主机状态 查看系统资源占用--top 磁盘信息监控--df--iostat 网络状态监控--sar -n DEV 12、环境…...
Linux笔记---进程:进程地址空间
1. 地址空间 程序地址空间是指程序在执行期间可以访问的内存范围。它由操作系统为每个进程分配,以确保进程之间不会相互干扰。地址空间包含了程序所需的所有内存区域,包括代码、已初始化和未初始化的数据、堆(heap)、栈ÿ…...
flutter in_app_purchase google支付 PG-GEMF-01错误
问题:PG-GEMF-01错误 flutter 使用in_app_purchase插件升降级订阅时报错PG-GEMF-01。 解决方案: 升降级订阅时,确保不调用 MethodCallHandlerImpl.java文件中的 setObfuscatedAccountId()方法、setObfuscatedProfileId()方法 原因…...
“精神内耗”的神经影像学证据:担忧和反刍会引发相似的神经表征
摘要 重复性消极思维(RNT)包括面向未来的担忧和面向过去的反刍,两者在认知和情感上具有相似的特征。这些不同但相关的过程在大多程度上会激活重叠的神经结构尚不确定,因为大多数神经科学研究只单独研究担忧或反刍。为了解决这个问题,本研究使…...
Linux--Debian或Ubuntu上扩容、挂载磁盘并配置lvm
一、三块12TB组RAID 5 可用容量约24TB 二、安装LVM工具(已安装请忽略) sudo apt-get install lvm2二、查看可用磁盘 sudo lsblk 或者 sudo fdisk -l三、创建物理卷(PV) 选中刚做的磁盘组 sudo pvcreat /dev/sdb1四、创建卷组…...
RocketMQ延迟消息机制
两种延迟消息 RocketMQ中提供了两种延迟消息机制 指定固定的延迟级别 通过在Message中设定一个MessageDelayLevel参数,对应18个预设的延迟级别指定时间点的延迟级别 通过在Message中设定一个DeliverTimeMS指定一个Long类型表示的具体时间点。到了时间点后…...
<6>-MySQL表的增删查改
目录 一,create(创建表) 二,retrieve(查询表) 1,select列 2,where条件 三,update(更新表) 四,delete(删除表…...
SciencePlots——绘制论文中的图片
文章目录 安装一、风格二、1 资源 安装 # 安装最新版 pip install githttps://github.com/garrettj403/SciencePlots.git# 安装稳定版 pip install SciencePlots一、风格 简单好用的深度学习论文绘图专用工具包–Science Plot 二、 1 资源 论文绘图神器来了:一行…...
uni-app学习笔记二十二---使用vite.config.js全局导入常用依赖
在前面的练习中,每个页面需要使用ref,onShow等生命周期钩子函数时都需要像下面这样导入 import {onMounted, ref} from "vue" 如果不想每个页面都导入,需要使用node.js命令npm安装unplugin-auto-import npm install unplugin-au…...
安卓基础(aar)
重新设置java21的环境,临时设置 $env:JAVA_HOME "D:\Android Studio\jbr" 查看当前环境变量 JAVA_HOME 的值 echo $env:JAVA_HOME 构建ARR文件 ./gradlew :private-lib:assembleRelease 目录是这样的: MyApp/ ├── app/ …...
代码随想录刷题day30
1、零钱兑换II 给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。 请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0 。 假设每一种面额的硬币有无限个。 题目数据保证结果符合 32 位带…...
20个超级好用的 CSS 动画库
分享 20 个最佳 CSS 动画库。 它们中的大多数将生成纯 CSS 代码,而不需要任何外部库。 1.Animate.css 一个开箱即用型的跨浏览器动画库,可供你在项目中使用。 2.Magic Animations CSS3 一组简单的动画,可以包含在你的网页或应用项目中。 3.An…...
Linux nano命令的基本使用
参考资料 GNU nanoを使いこなすnano基础 目录 一. 简介二. 文件打开2.1 普通方式打开文件2.2 只读方式打开文件 三. 文件查看3.1 打开文件时,显示行号3.2 翻页查看 四. 文件编辑4.1 Ctrl K 复制 和 Ctrl U 粘贴4.2 Alt/Esc U 撤回 五. 文件保存与退出5.1 Ctrl …...
Android屏幕刷新率与FPS(Frames Per Second) 120hz
Android屏幕刷新率与FPS(Frames Per Second) 120hz 屏幕刷新率是屏幕每秒钟刷新显示内容的次数,单位是赫兹(Hz)。 60Hz 屏幕:每秒刷新 60 次,每次刷新间隔约 16.67ms 90Hz 屏幕:每秒刷新 90 次,…...
SFTrack:面向警务无人机的自适应多目标跟踪算法——突破小尺度高速运动目标的追踪瓶颈
【导读】 本文针对无人机(UAV)视频中目标尺寸小、运动快导致的多目标跟踪难题,提出一种更简单高效的方法。核心创新在于从低置信度检测启动跟踪(贴合无人机场景特性),并改进传统外观匹配算法以关联此类检测…...
