【前端】JavaScript 变量引用、内存与数组赋值:深入解析三种情景
文章目录
- 💯前言
- 💯场景一:直接赋值与重新引用
- 为什么结果不是 `[3, 4, 5]`?
- 1. 引用与赋值的基本概念
- 2. 图示分析
- 关键总结
- 💯场景二:引用指向的变化
- 为什么结果还是 `[1, 2, 3]`?
- 对象引用与原始数据的区别
- 重新赋值与直接修改的差异
- 💯场景三:修改数组内容
- 为什么结果变成了 `[1, 2, 6]`?
- JavaScript 中的内存共享
- 💯深入理解 JavaScript 的内存模型与赋值行为
- 1. JavaScript 中的值类型和引用类型
- 2. 内存分配与垃圾回收
- 💯如何避免引用带来的问题
- 1. 浅拷贝与深拷贝
- 2. 使用 `Object.assign` 或 `lodash.cloneDeep`
- 💯小结
💯前言
- JavaScript 是一种基于对象的脚本语言,常用于前端开发。初学者在使用 JavaScript 时,通常会遇到一些关于变量引用和赋值的困惑。本文将详细讨论三种不同的代码场景,结合 JavaScript 的变量引用与内存模型,深入分析为什么这些代码输出会如此不同。希望通过对这些原理的探讨,能够帮助您更好地理解 JavaScript 中的变量引用机制。
💯场景一:直接赋值与重新引用
首先,我们来看以下的代码片段:
var arr = [1, 2, 3];
var newArr = arr;
newArr = [3, 4, 5];
console.log(arr); // 输出是什么?
运行结果:
[1, 2, 3]
为什么结果不是 [3, 4, 5]
?
要理解这个问题,我们需要深入理解 JavaScript 中的变量赋值与引用的区别。
1. 引用与赋值的基本概念
在 JavaScript 中,基本数据类型(如 number
、string
等)是按值传递的,而复杂数据类型(如数组、对象)是按引用传递的。
在代码中,var arr = [1, 2, 3]
创建了一个数组 [1, 2, 3]
,并且将其引用赋值给变量 arr
。此时,arr
保存的是数组在内存中的引用(地址),而不是数组的值本身。
然后,执行 var newArr = arr;
,这意味着 newArr
保存了与 arr
相同的引用。也就是说,newArr
和 arr
都指向了相同的内存地址,这个内存中存储的是数组 [1, 2, 3]
。
当我们执行 newArr = [3, 4, 5];
时,newArr
被重新赋值,指向了一个新的数组 [3, 4, 5]
的内存地址。此时,newArr
不再指向原来的数组 [1, 2, 3]
,而是指向了一个全新的数组。而 arr
依然保持指向原始数组的引用,因此打印 arr
时结果仍然是 [1, 2, 3]
。
2. 图示分析
-
初始状态:
arr
和newArr
都指向[1, 2, 3]
。
-
重新赋值后:
newArr
指向新的数组[3, 4, 5]
。arr
依然指向原数组[1, 2, 3]
。
关键总结
在 JavaScript 中,给一个变量赋予一个新的数组时,并不会改变原来的数组,而是创建了一个新的引用。如果希望改变所有引用同一数组的变量,那么需要对数组本身进行修改,而不是重新赋值。
💯场景二:引用指向的变化
接下来看第二个代码片段:
var arr = [1, 2, 3];
var newArr = arr;
arr = [4, 5, 6];
console.log(newArr); // 输出是什么?
运行结果:
[1, 2, 3]
为什么结果还是 [1, 2, 3]
?
在这个场景中,我们遇到了类似的问题。在执行 var newArr = arr;
之后,newArr
和 arr
都指向同一个数组 [1, 2, 3]
。但是,当执行 arr = [4, 5, 6];
时,arr
被重新赋值,指向了一个新的数组 [4, 5, 6]
。
需要注意的是,这种赋值不会影响到 newArr
,因为 newArr
依旧保持指向原来的数组 [1, 2, 3]
。简单来说,arr
重新指向了一个新的对象,而 newArr
还在指向原来的数组。
对象引用与原始数据的区别
在 JavaScript 中,对象、数组等复杂数据类型的变量并不直接保存数据的值,而是保存引用。当我们对变量重新赋值时,我们只是改变了它指向的内存地址,而原来的引用仍然有效。这也是为什么在打印 newArr
时,它依旧指向 [1, 2, 3]
。
重新赋值与直接修改的差异
如果我们希望改变 newArr
也能看到新数组的变化,就不能直接给 arr
重新赋值,而是需要修改数组本身的内容,比如:
arr.push(4);
这样,arr
和 newArr
都会看到新的内容。
💯场景三:修改数组内容
最后,我们来看第三个代码片段:
var arr = [1, 2, 3];
var newArr = arr;
arr[2] = 6;
console.log(newArr); // 输出是什么?
运行结果:
[1, 2, 6]
为什么结果变成了 [1, 2, 6]
?
在这里,我们需要理解的一个重要概念是“修改数组的内容”和“重新赋值”的区别。
var arr = [1, 2, 3];
创建了一个数组,并将其引用赋值给arr
。var newArr = arr;
将arr
的引用赋值给newArr
,此时arr
和newArr
都指向同一个数组。arr[2] = 6;
直接修改了数组的第三个元素。
由于 arr
和 newArr
都指向相同的数组,这意味着对数组内容的任何更改对这两个变量都是可见的。因此,当 arr[2]
被修改为 6
时,newArr
看到的也是修改后的数组 [1, 2, 6]
。
JavaScript 中的内存共享
在 JavaScript 中,数组和对象是通过引用来传递的。当多个变量引用同一个数组时,修改这个数组的内容将影响到所有引用该数组的变量。这种行为称为内存共享。
要理解内存共享,可以将数组或对象看作是存在于某个位置的数据块,而变量是指向这个数据块的“指针”。当我们通过一个变量修改数据块时,所有引用这个数据块的变量都会反映出相应的变化。
💯深入理解 JavaScript 的内存模型与赋值行为
为了更好地理解上述三种情况,我们还需要进一步了解 JavaScript 的内存管理和变量赋值行为。
1. JavaScript 中的值类型和引用类型
JavaScript 中的数据类型分为两类:
- 基本数据类型(值类型):包括
Number
、String
、Boolean
、Null
、Undefined
、Symbol
、BigInt
。这些类型的数据是按值传递的,这意味着每个变量都存储数据的副本。 - 引用数据类型:包括
Object
、Array
、Function
等。这些类型的数据是按引用传递的,变量保存的是对象的内存地址,而不是对象本身。
对于基本数据类型,变量赋值是直接复制值的副本,因此两个变量之间不会互相影响。对于引用类型,变量保存的是对象在内存中的地址,两个引用指向相同的地址意味着它们共享相同的内存内容。
2. 内存分配与垃圾回收
JavaScript 的内存分为两种主要区域:
- 栈内存(Stack Memory):用于存储基本类型的值和引用类型的引用。
- 堆内存(Heap Memory):用于存储引用类型的实际内容(对象、数组等)。
当执行赋值操作 var newArr = arr
时,newArr
和 arr
都指向堆内存中的同一个数组对象,因此对数组内容的修改对这两个变量来说是可见的。而当重新赋值 arr = [4, 5, 6]
时,arr
被重新赋予了一个新的引用,因此它和 newArr
分道扬镳。
💯如何避免引用带来的问题
在实际开发中,共享引用数据类型可能会带来一些不可预见的副作用,因此有时我们希望克隆数组或对象,以避免修改对其他变量产生影响。
1. 浅拷贝与深拷贝
浅拷贝 只复制对象的第一层引用,而 深拷贝 会递归复制所有嵌套的对象和数组。
-
浅拷贝的方法:使用
Object.assign()
或展开运算符...
。var arr = [1, 2, 3]; var shallowCopy = [...arr]; // 浅拷贝 shallowCopy[0] = 9; console.log(arr); // [1, 2, 3] console.log(shallowCopy); // [9, 2, 3]
-
深拷贝的方法:使用
JSON.parse(JSON.stringify(obj))
(这种方式有局限性,无法拷贝函数和某些特殊对象)。var obj = { a: 1, b: { c: 2 } }; var deepCopy = JSON.parse(JSON.stringify(obj)); deepCopy.b.c = 9; console.log(obj.b.c); // 2 console.log(deepCopy.b.c); // 9
2. 使用 Object.assign
或 lodash.cloneDeep
Object.assign()
可以用来实现对象的浅拷贝,而 lodash
库提供了一个更强大的深拷贝方法 _.cloneDeep()
,可以递归地复制嵌套的对象和数组。
💯小结
在 JavaScript 中,理解变量赋值、引用以及内存模型对于掌握语言的行为至关重要。在本文中,我们详细探讨了三种代码场景,并通过对比分析深入理解了以下几点:
- 变量赋值与引用:赋值为引用数据类型时,变量保存的是内存地址,而不是数据本身。因此,重新赋值并不会影响其他引用该数据的变量。
- 内存模型:JavaScript 中,栈内存用于存储基本类型和引用地址,而堆内存用于存储复杂对象的内容。对引用对象的操作会影响到所有指向该内存地址的变量。
- 修改数组内容与重新赋值:直接修改数组的内容会影响所有引用该数组的变量,而重新赋值则会让变量指向一个新的对象,不影响其他引用。
相关文章:

【前端】JavaScript 变量引用、内存与数组赋值:深入解析三种情景
博客主页: [小ᶻZ࿆] 本文专栏: 前端 文章目录 💯前言💯场景一:直接赋值与重新引用为什么结果不是 [3, 4, 5]?1. 引用与赋值的基本概念2. 图示分析 关键总结 💯场景二:引用指向的变化为什么…...
本地项目运行提示跨域问题
项目背景:我使用phpwebstudy在本地搭建了一个项目,然后前端是http://localhost:8080/ 后端我直接创建了一个本地域名,例如www.abc.com 然后vue.config.js配置如下,这个配置在我所有线上环境是没有任何问题的 devServer: {proxy…...

C++ —— string类(上)
目录 string的介绍 string类功能的使用介绍 constructor —— 构造 介绍使用(1)(2)(4) :构造、拷贝构造、带参构造 介绍(3):拷贝string类对象的一部分字符…...

React Native Mac 环境搭建
下载 Mac 版Android Studio 下载 安装 JDK 环境 Flutter 项目实战-环境变量配置一 安装 Node.js 方式一 通过Node.js 官网下载 下载完成后点击安装包进行安装 安装完成...
Python Web 开发的路径管理艺术:FastAPI 项目中的最佳实践与问题解析20241119
Python Web 开发的路径管理艺术:FastAPI 项目中的最佳实践与问题解析 引言:从路径错误到模块化管理的技术旅程 在现代 Python Web 开发中,路径管理是一个常常被忽视却非常重要的问题。尤其是在使用像 FastAPI 和 Tortoise ORM 这样的框架时…...

Rust derive macro(Rust #[derive])Rust派生宏
参考文章:附录 D:派生特征 trait 文章目录 Rust 中的派生宏 #[derive]基础使用示例:派生 Debug 派生其他常用特征示例:派生 Clone 和 Copy 派生宏的限制和自定义派生自定义派生宏上面代码运行时报错了,以下是解释 结论…...

springboot嗨玩旅游网站
摘 要 嗨玩旅游网站是一个专为旅行爱好者打造的在线平台。我们提供丰富多样的旅游目的地信息,包括景点信息、旅游线路、商品信息、社区信息、活动推广等,帮助用户轻松规划行程。嗨玩旅游网站致力于为用户提供便捷、实用的旅行服务,让每一次旅…...

杰发科技AC7840——EEP中RAM的配置
sample和手册中示例代码的sram区地址定义不一样 这个在RAM中使用没有限制,根据这个表格留下足够空间即可 比如需要4096字节的eep空间,可以把RAM的地址改成E000,即E000-EFFF,共4096bytes即可。...

从零开始的c++之旅——map_set的使用
1.序列式容器和关联式容器 序列式容器:逻辑结构为线性序列的数据结构,两个位置之间没有紧密的关系,比如两者交换一下还是序列式的容器,例如string,vector,deque,array等。 关联式容器࿱…...
Docker中的一些常用命令
find / -type f -name “文件名” 2>/dev/null 寻找所有目录中的这个文件 pwd 查看当前目录的地址 docker pull 镜像名 强制拉镜像 docker run 运行docker systemctl daemon-reload 关闭docker systemctl start docker 启动docker systemctl restart docker 重启docker /…...

自存 sql常见语句和实际应用
关于连表 查询两个表 SELECT * FROM study_article JOIN study_article_review 查询的就是两个表相乘,结果为两个表的笛卡尔积 相这样 这种并不是我们想要的结果 通常会添加一些查询条件 SELECT * FROM study_articleJOIN study_article_review ON study_art…...
python | argparse模块在命令行的使用中的重要作用
import argparseclass TestCases:def __init__(self, nameNone, expect_resultNone):self.name nameself.expect expect_resultself.parser argparse.ArgumentParser() # 创建命令解析器self.add_arguments() # 方法 : 添加命令self.args, _ self.parser.par…...

【HCIP]——OSPF综合实验
题目 实验需求 根据上图可得,实验需求为: 1.R5作为ISP:其上只能配置IP地址;R4作为企业边界路由器,出口公网地址需要通过PPP协议获取,并进行CHAP认证。(PS:因PPP协议尚未学习&#…...

PW系列工控电脑复制机:效率与精度双重提升
工控电脑复制应用:效率与精度的双重提升 随着现代企业对大数据、数据备份、和跨平台兼容性需求的快速增长,工控电脑已成为数据密集型产业的核心设备。针对工控环境中大量数据复制的特殊需求,PW系列NVMe/SATA PCIe SSD复制机(如PW…...

学习QT第二天
QT6示例运行 运行一个Widgets程序运行一个QT Quick示例 工作太忙了,难得抽空学点东西。-_-||| 博客中有错误的地方,请各位道友及时指正,感谢! 运行一个Widgets程序 在QT Creator的欢迎界面中,点击左侧的示例…...
11.20作业
题目一: 题目: // 数组的行列转置 代码: // 数组的行列转置 #include <stdio.h> int main() {int a[2][3], i, j, b[3][2];printf("输入一个两行三列的数组a:\n");for (i 0; i < 2; i)for (j 0; j < 3; j){scanf…...

Ubuntu Linux使用前准备动作_使用root登录图形化界面
Ubuntu默认是不允许使用 root 登录图形化界面的。这是出于安全考虑的设置。但如果有需要,可以通过以下步骤来实现使用 root 登录: 1、设置 root 密码 打开终端,使用当前的管理员账户登录系统。在终端中输入命令sudo passwd root,…...

DICOM核心概念:显式 VR(Explicit VR)与隐式 VR(Implicit VR)在DICOM中的定义与区别
在DICOM(Digital Imaging and Communications in Medicine)标准中,VR(Value Representation) 表示数据元素的值的类型和格式。理解显式 VR(Explicit VR)与隐式 VR(Implicit VR&#…...
源码分析Spring Boot (v3.3.0)
. ____ _ __ _ _/\\ / ____ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | _ | _| | _ \/ _ | \ \ \ \\\/ ___)| |_)| | | | | || (_| | ) ) ) ) |____| .__|_| |_|_| |_\__, | / / / /|_||___//_/_/_/:: Spring Boot :: (v3.3.0)//笔记背…...

IPv6 NDP 记录
NDP(Neighbor Discovery Protocol,邻居发现协议) 是 IPv6 的一个关键协议,它组合了 IPv4 中的 ARP、ICMP 路由器发现和 ICMP 重定向等协议,并对它们作出了改进。该协议使用 ICMPv6 协议实现,作为 IPv6 的基…...

黑马Mybatis
Mybatis 表现层:页面展示 业务层:逻辑处理 持久层:持久数据化保存 在这里插入图片描述 Mybatis快速入门 
云启出海,智联未来|阿里云网络「企业出海」系列客户沙龙上海站圆满落地
借阿里云中企出海大会的东风,以**「云启出海,智联未来|打造安全可靠的出海云网络引擎」为主题的阿里云企业出海客户沙龙云网络&安全专场于5.28日下午在上海顺利举办,现场吸引了来自携程、小红书、米哈游、哔哩哔哩、波克城市、…...

Cinnamon修改面板小工具图标
Cinnamon开始菜单-CSDN博客 设置模块都是做好的,比GNOME简单得多! 在 applet.js 里增加 const Settings imports.ui.settings;this.settings new Settings.AppletSettings(this, HTYMenusonichy, instance_id); this.settings.bind(menu-icon, menu…...

自然语言处理——Transformer
自然语言处理——Transformer 自注意力机制多头注意力机制Transformer 虽然循环神经网络可以对具有序列特性的数据非常有效,它能挖掘数据中的时序信息以及语义信息,但是它有一个很大的缺陷——很难并行化。 我们可以考虑用CNN来替代RNN,但是…...
【碎碎念】宝可梦 Mesh GO : 基于MESH网络的口袋妖怪 宝可梦GO游戏自组网系统
目录 游戏说明《宝可梦 Mesh GO》 —— 局域宝可梦探索Pokmon GO 类游戏核心理念应用场景Mesh 特性 宝可梦玩法融合设计游戏构想要素1. 地图探索(基于物理空间 广播范围)2. 野生宝可梦生成与广播3. 对战系统4. 道具与通信5. 延伸玩法 安全性设计 技术选…...
大语言模型(LLM)中的KV缓存压缩与动态稀疏注意力机制设计
随着大语言模型(LLM)参数规模的增长,推理阶段的内存占用和计算复杂度成为核心挑战。传统注意力机制的计算复杂度随序列长度呈二次方增长,而KV缓存的内存消耗可能高达数十GB(例如Llama2-7B处理100K token时需50GB内存&a…...

push [特殊字符] present
push 🆚 present 前言present和dismiss特点代码演示 push和pop特点代码演示 前言 在 iOS 开发中,push 和 present 是两种不同的视图控制器切换方式,它们有着显著的区别。 present和dismiss 特点 在当前控制器上方新建视图层级需要手动调用…...
快刀集(1): 一刀斩断视频片头广告
一刀流:用一个简单脚本,秒杀视频片头广告,还你清爽观影体验。 1. 引子 作为一个爱生活、爱学习、爱收藏高清资源的老码农,平时写代码之余看看电影、补补片,是再正常不过的事。 电影嘛,要沉浸,…...

打手机检测算法AI智能分析网关V4守护公共/工业/医疗等多场景安全应用
一、方案背景 在现代生产与生活场景中,如工厂高危作业区、医院手术室、公共场景等,人员违规打手机的行为潜藏着巨大风险。传统依靠人工巡查的监管方式,存在效率低、覆盖面不足、判断主观性强等问题,难以满足对人员打手机行为精…...