当前位置: 首页 > news >正文

【前端】特殊案例分析深入理解 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


代码执行的详细分析

在这里插入图片描述
接下来,我们逐步剖析代码的执行过程:

  1. 全局上下文的创建

    • 在全局环境中,变量 x 被声明并赋值为 10。这意味着全局作用域中存在一个变量 x,其值为 10,供整个程序访问。
  2. f1 函数的定义

    • f1 函数在全局作用域中被定义。在函数 f1 被创建的那一刻,JavaScript 引擎已经确定了 f1 的作用域链。也就是说,f1 拥有对全局作用域中所有变量的访问权限,包括变量 x
  3. fn2 函数的定义和调用

    • 函数 fn2 被定义,并且在其内部定义了一个局部变量 x,其值为 20。随后,fn2 调用了 f1
    • fn2 被调用时,局部变量 x 被赋值为 20,此时 fn2 中的 x 和全局的 x 是两个完全独立的变量,处于不同的作用域中。
    • 尽管在 fn2 内部调用了 f1,但是 f1 的作用域链是在其定义时确定的,它只包含了它自己和全局变量。因此,当 f1 试图访问变量 x 时,它只能访问全局的 x,输出结果为 10
      在这里插入图片描述

💯函数定义与调用的区别

理解 JavaScript 词法作用域的关键在于区分 函数的定义和调用。函数的定义位置决定了它的作用域链,而这种作用域链在函数执行时并不会因调用位置的不同而改变。

在前述代码中,函数 f1 是在全局作用域中定义的,因此它的作用域链包含了全局变量。当 f1 被调用时,无论它是从全局环境还是其他函数内部调用,它始终按照定义时的作用域链来解析变量。因此,即使 fn2 中定义了局部变量 x = 20f1 依然无法访问到 fn2 中的 x,因为 f1 的作用域链在其定义时已经固定,不包括 fn2 的局部变量。


💯动态作用域的对比

在这里插入图片描述
为了更好地理解词法作用域,我们不妨对比一下 动态作用域 的概念。动态作用域与词法作用域的最大区别在于,动态作用域基于函数的调用位置来决定变量的可见性。

若 JavaScript 使用的是动态作用域,那么在 fn2 中调用 f1 时,f1 会首先查找调用环境中的变量 x,也就是 fn2 中的 x,其值为 20。然而,JavaScript 并不是这样工作的。JavaScript 采用词法作用域,作用域链在函数定义时已经确定,而不会因调用位置的不同而改变。

动态作用域 意味着变量解析基于函数的调用栈,而不是其定义位置。换言之,在动态作用域语言中,函数在调用时会根据当前调用的上下文来决定变量的绑定关系。这与 JavaScript 的词法作用域完全不同,因为 JavaScript 在函数定义时就已确定了其作用域链。理解这种差异对于更好地掌握 JavaScript 的行为和作用域管理至关重要。


💯小结与拓展

在这里插入图片描述
通过这段代码,我们能够更深入地理解 JavaScript 中的一个核心概念:词法作用域函数的作用域链在函数被定义时就已经确定,而不是等到函数被调用时才动态地重新决定。这意味着函数始终根据它在代码中定义的位置来解析变量,而不是依据它在运行时的调用位置。这一特性使得 JavaScript 的作用域模型更为直观,但也可能导致在嵌套调用时出现令人困惑的行为。

为了更好地掌握这一概念,我们可以记住以下几点:

  1. 函数的作用域链在定义时确定,而不是在调用时确定
  2. 全局变量在任何函数中都可以被访问,除非函数中存在同名的局部变量。同名的局部变量只会在其函数内部遮蔽全局变量。
  3. 局部变量的作用域仅限于定义它们的函数内部,在函数外部无法访问,这有助于避免变量的意外冲突。
  4. 如果需要在函数调用时访问不同的变量,可以通过 闭包 来实现,但词法作用域的规则仍然适用。
  5. 闭包 是 JavaScript 中的一个重要概念,允许函数携带其定义时的作用域。这使得函数即使在离开定义环境后,仍然可以访问该作用域中的变量,这是理解 JavaScript 词法作用域的重要一环。

闭包 的强大在于它允许创建私有变量、记住函数的执行状态以及实现函数工厂等功能。在 JavaScript 中,闭包的应用极为广泛,尤其是在处理异步操作、回调函数 等情境时,闭包的特性能够确保函数可以访问定义时的作用域。

理解 JavaScript词法作用域 对于编写健壮、可维护的代码至关重要。尤其是在处理嵌套函数、回调函数或模块化代码时,深入掌握作用域规则可以帮助我们避免许多潜在的错误。例如,在编写回调函数时,我们可能在无意识中引用了全局变量或外部函数的变量。如果对 词法作用域 的理解不够深入,这些问题往往难以察觉。

此外,立即执行函数表达式(IIFE) 是一种常用技术,用于在 JavaScript 中创建局部作用域,防止变量污染全局环境。通过 IIFE,我们可以在函数内部创建私有变量,从而避免它们泄漏到全局作用域中。这对代码的维护性和可读性具有非常重要的意义。IIFE 充分利用了 JavaScript 的词法作用域机制,为开发者提供了一个简洁的解决方案来隔离作用域。

另外,ES6 引入的 letconst 关键字 进一步增强了对作用域的控制。这些关键字使得变量具有块级作用域,避免了传统 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视图,将其去除前端如果通过异步调用的方式进行交互&#xff0…...

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下查看函数 函数名称命名规则&#xff1a;EXIT_<程序名>_<序号> ③使用函数查找&#xff1a;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】&#xff1c;NCard&#xff1e; 标签 【VUE3】【Naive UI】&#xff1c;n-button&#xff1e; 标签 【VUE3】【Naive UI】&#xff1c;a&#xff1e; 标签 【VUE3】【Naive UI】&#xff1c;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. 地址空间 程序地址空间是指程序在执行期间可以访问的内存范围。它由操作系统为每个进程分配&#xff0c;以确保进程之间不会相互干扰。地址空间包含了程序所需的所有内存区域&#xff0c;包括代码、已初始化和未初始化的数据、堆&#xff08;heap&#xff09;、栈&#xff…...

flutter in_app_purchase google支付 PG-GEMF-01错误

问题&#xff1a;PG-GEMF-01错误 flutter 使用in_app_purchase插件升降级订阅时报错PG-GEMF-01。 解决方案&#xff1a; 升降级订阅时&#xff0c;确保不调用 MethodCallHandlerImpl.java文件中的 setObfuscatedAccountId()方法、setObfuscatedProfileId()方法 原因&#xf…...

“精神内耗”的神经影像学证据:担忧和反刍会引发相似的神经表征

摘要 重复性消极思维(RNT)包括面向未来的担忧和面向过去的反刍&#xff0c;两者在认知和情感上具有相似的特征。这些不同但相关的过程在大多程度上会激活重叠的神经结构尚不确定&#xff0c;因为大多数神经科学研究只单独研究担忧或反刍。为了解决这个问题&#xff0c;本研究使…...

Linux--Debian或Ubuntu上扩容、挂载磁盘并配置lvm

一、三块12TB组RAID 5 可用容量约24TB 二、安装LVM工具&#xff08;已安装请忽略&#xff09; sudo apt-get install lvm2二、查看可用磁盘 sudo lsblk 或者 sudo fdisk -l三、创建物理卷&#xff08;PV&#xff09; 选中刚做的磁盘组 sudo pvcreat /dev/sdb1四、创建卷组…...

浅谈 React Hooks

React Hooks 是 React 16.8 引入的一组 API&#xff0c;用于在函数组件中使用 state 和其他 React 特性&#xff08;例如生命周期方法、context 等&#xff09;。Hooks 通过简洁的函数接口&#xff0c;解决了状态与 UI 的高度解耦&#xff0c;通过函数式编程范式实现更灵活 Rea…...

Vue记事本应用实现教程

文章目录 1. 项目介绍2. 开发环境准备3. 设计应用界面4. 创建Vue实例和数据模型5. 实现记事本功能5.1 添加新记事项5.2 删除记事项5.3 清空所有记事 6. 添加样式7. 功能扩展&#xff1a;显示创建时间8. 功能扩展&#xff1a;记事项搜索9. 完整代码10. Vue知识点解析10.1 数据绑…...

React第五十七节 Router中RouterProvider使用详解及注意事项

前言 在 React Router v6.4 中&#xff0c;RouterProvider 是一个核心组件&#xff0c;用于提供基于数据路由&#xff08;data routers&#xff09;的新型路由方案。 它替代了传统的 <BrowserRouter>&#xff0c;支持更强大的数据加载和操作功能&#xff08;如 loader 和…...

SCAU期末笔记 - 数据分析与数据挖掘题库解析

这门怎么题库答案不全啊日 来简单学一下子来 一、选择题&#xff08;可多选&#xff09; 将原始数据进行集成、变换、维度规约、数值规约是在以下哪个步骤的任务?(C) A. 频繁模式挖掘 B.分类和预测 C.数据预处理 D.数据流挖掘 A. 频繁模式挖掘&#xff1a;专注于发现数据中…...

聊聊 Pulsar:Producer 源码解析

一、前言 Apache Pulsar 是一个企业级的开源分布式消息传递平台&#xff0c;以其高性能、可扩展性和存储计算分离架构在消息队列和流处理领域独树一帜。在 Pulsar 的核心架构中&#xff0c;Producer&#xff08;生产者&#xff09; 是连接客户端应用与消息队列的第一步。生产者…...

unix/linux,sudo,其发展历程详细时间线、由来、历史背景

sudo 的诞生和演化,本身就是一部 Unix/Linux 系统管理哲学变迁的微缩史。来,让我们拨开时间的迷雾,一同探寻 sudo 那波澜壮阔(也颇为实用主义)的发展历程。 历史背景:su的时代与困境 ( 20 世纪 70 年代 - 80 年代初) 在 sudo 出现之前,Unix 系统管理员和需要特权操作的…...

Map相关知识

数据结构 二叉树 二叉树&#xff0c;顾名思义&#xff0c;每个节点最多有两个“叉”&#xff0c;也就是两个子节点&#xff0c;分别是左子 节点和右子节点。不过&#xff0c;二叉树并不要求每个节点都有两个子节点&#xff0c;有的节点只 有左子节点&#xff0c;有的节点只有…...

优选算法第十二讲:队列 + 宽搜 优先级队列

优选算法第十二讲&#xff1a;队列 宽搜 && 优先级队列 1.N叉树的层序遍历2.二叉树的锯齿型层序遍历3.二叉树最大宽度4.在每个树行中找最大值5.优先级队列 -- 最后一块石头的重量6.数据流中的第K大元素7.前K个高频单词8.数据流的中位数 1.N叉树的层序遍历 2.二叉树的锯…...

【 java 虚拟机知识 第一篇 】

目录 1.内存模型 1.1.JVM内存模型的介绍 1.2.堆和栈的区别 1.3.栈的存储细节 1.4.堆的部分 1.5.程序计数器的作用 1.6.方法区的内容 1.7.字符串池 1.8.引用类型 1.9.内存泄漏与内存溢出 1.10.会出现内存溢出的结构 1.内存模型 1.1.JVM内存模型的介绍 内存模型主要分…...

为什么要创建 Vue 实例

核心原因:Vue 需要一个「控制中心」来驱动整个应用 你可以把 Vue 实例想象成你应用的**「大脑」或「引擎」。它负责协调模板、数据、逻辑和行为,将它们变成一个活的、可交互的应用**。没有这个实例,你的代码只是一堆静态的 HTML、JavaScript 变量和函数,无法「活」起来。 …...