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

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

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

背景

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

function foo() {var a = 1var b = aa = 2console.log(a)console.log(b)
}
foo()
// 执行结果:
// 2
// 1
function foo() {var a = {name: 'yy'}var b = aa.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 = avar 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 = 1const test2 = 2var 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 并没有被销毁,而是保存在内存中,现在我们站在内存模型的角度来分析这段代码的执行流程:

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Windows 2012 R2 单网卡安装 PPTPVP*

环境 1、服务器:Windows Server 2012 R2(虚拟机) 内网IP:10.3.0.233 2、客户端:Windows 11 需求:当客户端通过VPN连接后,只有当访问 10.3.0.0 网段的数据包才走 VPN 通道后,其它…

python 计数器

这个Python脚本定义了一个名为new_counter()的函数,它读取系统时间并将其与存储在文件中的时间进行比较。然后根据比较结果更新存储在另一个文件中的计数器值。如果系统时间与存储的时间匹配,则计数器值增加1。如果系统时间与存储的时间不匹配&#xff0…

软件测试|MySQL CROSS JOIN:交叉连接的详细解析

简介 在 MySQL 数据库中,CROSS JOIN 是一种用于生成两个或多个表的笛卡尔积的连接方法。CROSS JOIN 不需要任何连接条件,它将左表的每一行与右表的每一行进行组合,从而生成一个包含所有可能组合的结果集。本文将详细介绍 MySQL 中的 CROSS J…

SpringMVC 学习博客记录

文章目录 博客记录请求转发和请求包含request.getRequestDispatcher() 源码学习知识点记录 博客记录 Handler、HandlerMapping和HandlerAdapter作用及区别 请求转发和请求包含 request.getRequestDispatcher() getRequestDispatcher()包含两个重要方法,分别是请…

【大数据OLAP引擎】StartRocks存算分离

存算分离的原因 降低存储成本:同样的存储大小对象存储价格只有SSD的1/10,所以号称存储成本降低80%不是吹的。 存算一体到存算分离 存算一体 作为 MPP 数据库的典型代表,StarRocks 3.0 版本之前使用存算一体 (shared-nothing) 架构&#xf…

用Linux的视角来理解缓冲区概念

缓冲区的认识 缓冲区(buffer)是存储数据的临时存储区域。当我们用C语言向文件中写入数据时,数据并不会直接的写到文件中,中途还经过了缓冲区,而我们需要对缓冲区的数据进行刷新,那么数据才算写到文件当中。…

基于JavaWeb+BS架构+SpringBoot+Vue校车调度管理系统的设计和实现

基于JavaWebBS架构SpringBootVue校车调度管理系统的设计和实现 文末获取源码Lun文目录前言主要技术系统设计功能截图订阅经典源码专栏Java项目精品实战案例《500套》 源码获取 文末获取源码 Lun文目录 摘 要 1 Abstract 1 目 录 2 1 绪 论 1 1.1研究背景 1 1.2 研究意义 1 1.…

YOLOv8优化策略:轻量化改进 | MobileNetV3,轻量级骨架首选

🚀🚀🚀本文改进:MobileNetV3的创新点包括:使用自适应瓶颈宽度、借鉴SENet中的Squeeze-and-Excitation机制、引入h-swish激活函数等。 🚀🚀🚀YOLOv8改进专栏:http://t.csdnimg.cn/hGhVK 学姐带你学习YOLOv8,从入门到创新,轻轻松松搞定科研; 1.MobileNetV3介…

牛客字符串

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、pandas是什么?二、使用步骤 1.引入库2.读入数据总结 前言 提示:这里可以添加本文要记录的大概内容: 例如:…

【题解】—— LeetCode一周小结1

1.经营摩天轮的最大利润 题目链接: 1599. 经营摩天轮的最大利润 你正在经营一座摩天轮,该摩天轮共有 4 个座舱 ,每个座舱 最多可以容纳 4 位游客 。你可以 逆时针 轮转座舱,但每次轮转都需要支付一定的运行成本 runningCost 。摩…

【算法每日一练]-dfs (保姆级教程 篇9) #俄罗斯方块 #ABC Puzzle #lnc的工资

目录 今日知识点: 二维图形的状态压缩,存下所有的合法状态然后暴力遍历 dfs的优化剪枝 二项式定理 俄罗斯方块 ABC Puzzle lnc的工资 俄罗斯方块 322D 题意:在4*4方格中分别给出3个俄罗斯方块,问是否可以经过旋转&#xf…

vue3基础类型和引用类型,和store的使用

案例一: 如果我在store创建一个变量,是读取缓存key为name的数据, store.name 默认值是张三 # 声明一个变量 const title ref(store.name) # 然后修改title.value "李四", # 问:打印store.name&#xff0…