JS栈和堆:数据是如何存储的

JS栈和堆:数据是如何存储的

  • 背景
  • JavaScript 是什么类型的语言
  • JavaScript 的数据类型
  • 内存空间
    • 栈空间和堆空间
    • 再谈闭包

背景

JS有多种数据类型:数字型,字符串型,数组型等,虽然 JavaScript 并不需要直接去管理内存,但是实际项目中为了能避开一些不必要的坑,还是需要了解数据在内存中的存储方式。例如下面两段示例代码:

function foo() {
  var a = 1
  var b = a
  a = 2
  console.log(a)
  console.log(b)
}
foo()
// 执行结果:
// 2
// 1
function foo() {
  var a = {name: 'yy'}
  var b = a
  a.name = 'qq'
  console.log(a)
  console.log(b)
}
foo()
// 执行结果:
// {name: 'qq'}
// {name: 'qq'}

第一段代码没什么难以理解的,但是如果你对第二段代码感到迷惑,想要彻底弄清楚这个问题,就需要先从 JavaScript 这种语言说起。

JavaScript 是什么类型的语言

每种编程语言都具有内建的数据类型,但不同语言它们的数据类型常有不同之处,使用方式也很不一样。比如 C 语言在定义变量之前,就需要确定变量的类型,我们将这种称为静态语言。相反地,像 JavaScript 这种在运行过程中需要检查数据类型的语言称为动态语言

虽然 C 语言是静态语言,但是在 C 语言中我们可以把其他类型数据赋予给一个声明好的变量,比如将 int 型变量赋值给 bool 型变量,因为在赋值过程中,C 编译器会把 int 型的变量悄悄转换为 bool 型的变量,通常将这种偷偷转换的操作称为隐式类型转换,而支持隐式类型转换的语言称为弱类型语言,不支持隐式类型转换的语言称为强类型语言。这这点上,C 和 JavaScript 都是弱类型语言。

在这里插入图片描述

JavaScript 的数据类型

现在我们知道,JavaScript 是一种弱类型、动态语言。意味着:

  • 弱类型:你不需要告诉 JavaScript 引擎这个变量是什么数据类型,JavaScript 引擎在运行代码时会自己计算出来
  • 动态:你可以使用同一个变量保存不同类型的数据(变量没有数据类型,值才有数据类型)

JavaScript 中的数据类型一共有 8 种,它们分别是:

类型描述
Booleantruefalse 两个值
Nullnull
Undefined一个没有被赋值的变量会有个默认值 undefined,变量提升时的默认值也是 undefined
Number数字型
BigIntJavaScript 中的任意精度整数,可以安全存储和操作大整数,即使超出 Number 能够表示的安全整数范围。(Number 数据类型大于或等于 2 的 1024 次方的数值 JavaScript 无法表示,会返回 infinity,ES2020 引入一种新的数据类型 BigInt 来解决这个问题)
String字符串
Symbol符号类型是唯一的并且是不可修改的,通常用来作为 Object 的 key
Object在 JavaScript 中,对象可以看作是一组属性的集合

需要注意的是:

  • 使用 typeof 检测 null 时,返回的是 object,这是当初 JavaScript 语言的一个 Bug,为了兼容老的代码所以一直保留至今
  • Object 类型比较特殊,它是由上述 7 种类型组成的一个包含了 key,value键值对的数据类型
  • 我们把前面 7 中数据类型称为原始类型,最后一个对象类型称为引用类型,因为它们在内存中存放的位置不一样。

内存空间

在 JavaScript 执行过程中,只要有三种类型内存空间,分别是代码空间栈空间堆空间。其中的代码空间主要是存储可执行代码。今天主要来介绍栈空间和堆空间。

栈空间和堆空间

这里的栈空间就是在 JS 调用栈 文中反复提及的调用栈,先来看下面这段示例代码:

function foo() {
  var a = 'yy'
  var b = a
  var c = {name: 'qq'}
  var d = c
}
foo()

在 JS 调用栈 这篇文章中讲解过,当执行一段代码时,需要先编译并创建执行上下文,然后再按照顺序执行代码。下图是执行到 b = a 这行代码时其调用栈的状态图,可以参考:

在这里插入图片描述

此时,变量 a 和 变量 b 的值都被保存在执行上下文中,而执行上下文又被压入栈中,所以也可以认为变量 a 和变量 b 的值都是存放在栈中的。

接下来继续执行 c = {name: 'qq'} 这行代码,由于 JavaScript 引擎判断右边的值是一个引用类型,此时 JavaScript 引擎不是直接将该对象存放在变量环境中,而是将它分配到堆空间里,分配后该对象会有一个在“堆”中的地址,然后再将该数据的地址写进 c 的变量值,最终分配好内存的示例图如下:

在这里插入图片描述

从上图可以清晰观察到,对象类型时存放在堆空间的,栈空间只保留了对象的引用地址,当 JavaScript 需要访问该数据时,是通过栈中的引用地址来访问的。

为何一定要分“堆”和“栈”两个存储空间呢?所有数据直接都存放在“栈”中可以么?
不可以。因为 JavaScript 引擎需要用栈来维护程序执行期间上下文的状态,如果栈空间大了所有数据都存放在栈空间中,会影响到上下文切换的效率,进而影响到整个程序的执行效率。
例如文中的 foo 函数执行结束了,JavaScript 引擎需要离开当前的执行上下文,只需要将指针下移到全局上下文的地址就行了,foo 函数执行上下文栈区空间全部回收。所以,通常情况下,栈空间都不会设置太大。

在 JavaScript 中,赋值操作和其他语言有很大的不同,原始类型的赋值会完整复制变量值,而引用类型的赋值是复制引用地址。所以 d = c 这行代码的操作就是把 c 的引用地址赋值给了 d,具体可参考下图:

在这里插入图片描述

从图中看出,变量 c 和变量 d 都指向了同一个堆中的对象。这就很好解释了在文章开头的示例代码 2 中,通过 a 修改 name 的值,变量 b 的值也会跟着改变,因为归根到底它们是同一个对象。

再谈闭包

在知道了作用域内的原始数据类型会被存储到栈空间,引用类型会被存储到堆空间,基于这两点的认知,进一步探讨下闭包的内存模型。

关于什么是闭包,可以参考文章 JS作用域链和闭包 这篇文章。

还是以这段代码为例:

function foo() {
  var myname = 'yy'
  let test1 = 1
  const test2 = 2
  var innerbar = {
    getName: function() {
      console.log(test1)
      return myname
    },
    setName: function(newName) {
      myname = newName
    }
  }
  return innerbar
}
var bar = foo()
bar.setName('qq')
bar.getName()
console.log(bar.getName())

由于变量 mynametest1test2 都是原始类型数据,所以在执行 foo 函数时,它们会被压入到调用栈中;当 foo 函数执行结束后,调用栈中的 foo 函数的执行上下文会被销毁,其内部变量 mynametest1test2 也应该一同被销毁。但是根据 JS作用域链和闭包 文中的分析,由于 foo 函数产生了闭包,所以变量 mynametest1 并没有被销毁,而是保存在内存中,现在我们站在内存模型的角度来分析这段代码的执行流程:

  1. 当 JavaScript 引擎执行到 foo 函数时,首先会编译并创建一个空的执行上下文
  2. 编译过程中遇到内部函数 setName,JavaScript 引擎还要对内部函数做一次快速的词法扫描,发现该内部函数引用了 foo 函数中的 myname 变量,由于是内部函数引用了外部函数的变量,所以 JavaScript 引擎判断这是一个闭包,于是在堆空间创建一个"closure(foo)" 对象(这是一个内部对象,JavaScript 无法访问),用来保存 myname 变量
  3. 接着继续扫描到 getName 方法,发现函数内部引用了变量 test1,于是 JavaScript 引擎又将 test1 添加到 "closure(foo)" 对象中
  4. 此时,堆中的 "closure(foo)" 对象中就包含了 mynametest1 两个变量了
  5. 由于 test2 并没有被内部函数引用,所以它依旧保存在调用栈中

通过以上分析,可以画出执行到 foo 函数中 return innerbar 语句时的调用栈状态图如下:

在这里插入图片描述

当执行到 foo 函数时,闭包就产生了;当 foo 函数执行结束之后返回的 getNamesetName 方法都引用了 "closure(foo)" 对象,所以即使 foo 函数退出了,"closure(foo)" 依然被其内部的 setNamegetName 方法引用,所以在下次调用 bar.setNamebar.getName 时,创建的执行上下文中就包含了 "closure(foo)"

总的来说,产生闭包的两个核心步骤:第一步是需要预扫描内部函数,第二步是把内部函数引用的外部变量保存到堆中。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【软件测试】刚入行的测试人,“我“该怎么提升自己技术能力...

目录:导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结(尾部小惊喜) 前言 一个问题&#xf…

读《Mixtral of Experts》

摘要 稀疏混合专家(SMoE) 语言模型Mixtral 8x7B(那大概可以理解成他是一个缝合怪,把所有的任务模型缝合到一起,然后有一个类似打分投票的路由机制来针对输入问题选择任务子模型从而得到针对性的结果。)。Mi…

PHP短链接url还原成长链接

在开发过程中,碰到了需要校验用户回填的短链接是不是系统所需要的,于是就需要还原找出短链接所对应的长链接。 长链接转短链接 在百度上搜索程序员,跳转页面后的url就是一个长链接。当然你可以从任何地方复制一个长链接过来。 长链接 http…

高效降压控制器FP7132XR:为高亮度LED提供稳定可靠的电源

目录 一. FP7132概述 二. 驱动电路:FP7132 三. FP7132应用 高亮度LED作为新一代照明技术的代表,已经广泛应用于各种领域。然而,高亮度LED的工作电压较低,需要一个高效降压控制器来为其提供稳定可靠的电源。在众多降压控制器…

ubuntu20.04 扩大交换空间swap

检查当前swap情况 free -msudo swapon --show关闭现有的swap sudo swapoff -a创建一个新的swap文件 sudo fallocate -l 32G /swapfile设定正确的权限 sudo chmod 600 /swapfile下面这个指令会把我们的空间变成可用的swap空间 sudo mkswap /swapfile启用swap文件 sudo swa…

电源模块常见温升测试方法分享 -纳米软件

温升测试是电器产品安规测试项目之一,是为了检测电器产品及部件的温度变化情况,判断是否符合要求。在设备运行过程中会释放一定的热量,如果内部温度过高会影响产品的性能和稳定性,导致绝缘性能下降,因此温升测试是确保…

【LangChain学习之旅】—(4) 模型I/O:输入提示、调用模型、解析输出

【LangChain学习之旅】—(4) 模型I/O:输入提示、调用模型、解析输出 Model I/OLangChain 中提示模板的构建语言模型为什么选择langchain输出解析总结 Reference:LangChain 实战课 Model I/O 我们可以把对模型的使用过程拆解成三块…

iPad Pro如何使用SSH远程连接服务器云端编程开发【内网穿透】

文章目录 1. 在iPad下载Code APP2.安装cpolar内网穿透2.1 cpolar 安装2.2 创建TCP隧道 3. iPad远程vscode4. 配置固定TCP端口地址4.1 保留固定TCP地址4.2 配置固定的TCP端口地址4.3 使用固定TCP地址远程vscode 正文开始前给大家推荐个网站,前些天发现了一个巨牛的 …

GSEQ行为序列分析软件学习汇总

0、问题描述: 1、GSEQ软件是做什么的?2、GSEQ软件如何使用?2、GSEQ软件前期需要在“记事本”中编写“程式码”需要将所有行为数据编码之后,将编码行为序列粘贴到GSEQ软件中去,如果数据量很大,这个过程就非…

基于ssm物流配送人员车辆调度管理系统的设计与实现+vue论文

摘 要 如今社会上各行各业,都喜欢用自己行业的专属软件工作,互联网发展到这个时候,人们已经发现离不开了互联网。新技术的产生,往往能解决一些老技术的弊端问题。因为传统物流配送人员车辆调度信息管理难度大,容错率低…

华为端口安全常用3种方法配置案例

安全动态mac地址学习功能 [Huawei]int g0/0/01 interface GigabitEthernet0/0/1 port-security enable //开启安全 port-security max-mac-num 2 //最多为2个mac地址学习 port-security protect-action restrict //丢包带警告 port-security aging-time 1 //mac地址的老化时间…

OpenAI 正式上线 GPT 商店

ChatGPT 商店上线,OpenAI 正式推出 GPT Store 北京时间 1 月 11 日,在经历了一个月的推迟后,OpenAI 在周三正式推出了 GPT Store。借助这一在线商店,OpenAI 用户可以分享定制版 ChatGPT 聊天机器人。 OpenAI 称,GPT …

uniapp回到上一页

回到上一页代码uniapp中代码 <template><view class"my_footer"><view class"m_goBack" click.stop"handlergoBack()"></view><view class"m_index" click.stop"handlergoIndex()"></v…

微信小程序的生命周期函数有哪些?

面试官&#xff1a;说说微信小程序的生命周期函数有哪些&#xff1f; 一、是什么 跟vue、react框架一样&#xff0c;微信小程序框架也存在生命周期&#xff0c;实质也是一堆会在特定时期执行的函数 小程序中&#xff0c;生命周期主要分成了三部分&#xff1a; 应用的生命周期…

如何在IntelliJ IDEA中配置SSH服务器开发环境并实现固定地址远程连接

文章目录 1. 检查Linux SSH服务2. 本地连接测试3. Linux 安装Cpolar4. 创建远程连接公网地址5. 公网远程连接测试6. 固定连接公网地址7. 固定地址连接测试 本文主要介绍如何在IDEA中设置远程连接服务器开发环境&#xff0c;并结合Cpolar内网穿透工具实现无公网远程连接&#xf…

C++_vector类

目录 一、vector的模拟实现 1、vector的组成结构 2、vector尾插数据 2.1 析构函数 3、迭代器实现 4、resize 5、删除数据 5.1 迭代器失效 6、指定位置插入数据 6.1 迭代器失效 7、迭代器构造和resize构造 8、深浅拷贝 结语&#xff1a; 前言&#xff1a; vect…

计算机二级WPS Office考试须知、如何备考、备考建议

本文从计算机二级考试须知、如何备考、备考建议以及考试的注意事项等方面进行讲述&#xff0c;认认真真看&#xff0c;对你受益匪浅。 一、计算机二级考试须知 2021年3月27日至29日将举办第60次全国计算机等级考试&#xff08;NCRE&#xff09;&#xff0c;从今年起&#xff0…

加速你的数据库:公司中SQL优化的八大绝招

前言 SQL优化这个问题也是老生常谈了&#xff0c;很多都还是在八股文中了解到&#xff0c;那么公司里的SQL都是咋优化的呢&#xff1f;和八股文一样吗&#xff1f;下面&#xff0c;我将与大家分享我在公司里学到的SQL优化知识。SQL优化是提高数据库性能和减少资源消耗的重要一环…

设计模式之空对象模式

目录 1.简介 2.结构图 3.实例 4.优缺点 1.简介 空对象模式也是我们平时编程用的比较多的一种行为型设计模式&#xff0c;它的宗旨在解决空对象引起的异常报错问题&#xff1b;在空对象模式&#xff08;Null Object Pattern&#xff09;中&#xff0c;一个空对象取代 NULL 对…

【一文详解】知识分享:(MySQL关系型数据库快速入门)

mysql基础 数据类型 整型 类型名称取值范围大小TINYINT-128〜1271个字节SMALLINT-32768〜327672个宇节MEDIUMINT-8388608〜83886073个字节INT (INTEGHR)-2147483648〜21474836474个字节BIGINT-9223372036854775808〜92233720368547758078个字节 注: 无符号在数据类型后加 un…