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 并没有被销毁,而是保存在内存中,现在我们站在内存模型的角度来分析这段代码的执行流程:

  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)"

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

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

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

相关文章

Apache ActiveMQ RCE CNVD-2023-69477 CVE-2023-46604

漏洞简介 Apache ActiveMQ官方发布新版本,修复了一个远程代码执行漏洞,攻击者可构造恶意请求通过Apache ActiveMQ的61616端口发送恶意数据导致远程代码执行,从而完全控制Apache ActiveMQ服务器。 影响版本 Apache ActiveMQ 5.18.0 before …

linux kernel:devres模块架构分析

参考文档: https://www.kernel.org/doc/html/latest/driver-api/driver-model/devres.html https://www.cnblogs.com/sammei/p/3498052.html devres in linux driver devres: Managed Device Resource device resource managementdevres_alloc()动态申请内存分配…

好包不等待:用 pnpm 加速你的项目依赖

欢迎来到我的博客,代码的世界里,每一行都是一个故事 好包不等待:用 pnpm 加速你的项目依赖 前言什么是pnpm背景和诞生原因:与传统 npm 安装方式的区别: 基础用法安装依赖:卸载依赖:安装全局依赖…

2024百元蓝牙耳机测评推荐,百元超强的开放式蓝牙耳机合集

现在的蓝牙耳机市场真的是太卷了,各种品牌、各种型号让人挑得眼花缭乱,但你知道吗?其实在百元价位里也有很多好货。今天,我就来给大家好好测评几款2024年的百元级蓝牙耳机,看看哪些是真正的性价比之王,开放…

基于JAVA的康复中心管理系统 开源项目

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 普通用户模块2.2 护工模块2.3 管理员模块 三、系统展示四、核心代码4.1 查询康复护理4.2 新增康复训练4.3 查询房间4.4 查询来访4.5 新增用药 五、免责说明 一、摘要 1.1 项目介绍 基于JAVAVueSpringBootMySQL的康复中…

小微企业适用什么样的CRM系统?CRM选型有哪些技巧?

小型企业主的日子着实不好过,从营销和销售到客户支持和保留,这些基本都要亲力亲为,才能确保将客户放在首位。如果您要是一个小企业主,那么相信您能懂这个感觉,恨不得自己长出八只手,才能让一切井井有条——…

Wordpress网站开发问题解决——除了主页之外的所有页面都是“找不到页面内容”(修复记录)

一条纯经验操作 引言慌火上浇油后台查看 解决之路结尾 引言 最近 阿里云老是提醒我边缘计算机控制升级 我自己建立了一个网站,用的就是阿里云的万网服务器 所以 我去看看 结果跟我没什么关系 本以为就这么愉快地结束了 没想到 我建立的网站就只能打开主页 其他页…

羊奶加红枣,女性必备的加法!

羊奶加红枣,女性必备的加法! 在当今社会,女性健康备受关注。而羊奶和红枣作为常见的食材,以其独特的营养价值备受追捧。不少人认为,羊奶里面加红枣对女生有很大的帮助。那么,到底羊奶加红枣对女性健康有哪…

06.构建大型语言模型步骤

在本章中,我们为理解LLMs奠定了基础。在本书的其余部分,我们将从头开始编写一个代码。我们将以 GPT 背后的基本思想为蓝图,分三个阶段解决这个问题,如图 1.9 所示。 图 1.9 本书中介绍的构建LLMs阶段包括实现LLM架构和数据准备过程、预训练以创建基础模型,以及微调基础模…

无重复字符的最长字串

题目 给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。 示例 示例 1: 输入: s "abcabcbb" 输出: 3 解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。示例 2: 输入: s "bbbbb" 输出: 1 解…

PMP报考流程?

项目管理相关证书,PMP是一个不错的选择,尤其是小白朋友。 PMP 是项目管理的一个入门证书,理论知识很全面,涵盖了项目管理的全流程,可作为日常工具参考书、完全小白的可以先考一个PMP学好理论,再考一个PRIN…

用友U8流程审批效率-SQLServer+SSRS

文章目录 @[TOC]1、 需求及效果1.1 需求1.2 效果2、 思路及SQL语句3、实现折叠明细表4、结语1、 需求及效果 1.1 需求 想要查看U8的审批流程,查看流程在哪个节点或人停留的时间,这个单据整个流程走下来需要的时间。可以更加直观方便的查看审批效率 1.2 效果 采用了SSRS上…

【漏洞复现】大华 DSS 数字监控系统 itcBulletin SQL 注入

漏洞描述 大华 DSS存在SQL注入漏洞,攻击者 pota/services/itcBuletin 路由发送特殊构造的数据包,利用报错注入获取数据库敏感信息。攻击者除了可以利用 SQL注入漏词获取数据库中的信息例如,管理员后台密码、站点的用户人人信息)之外,甚至在高权限的情况可向服务器中写入木…

HNU-数据库系统-作业

数据库系统-作业 计科210X 甘晴void 202108010XXX 第一章作业 10.09 1.(名词解释)试述数据、数据库、数据库管理系统、数据库系统的概念。 数据,是描述事物的符号记录。 数据库(DB),是长期存储在计算机内、有组织、可共享的大量…

已签名驱动程序安装后提示“Windows无法验证此设备所需驱动程序数字签名”的原因和解决方法

在Windows 64位系统上,正常开启数字签名认证时,驱动程序软件需要经过微软数字签名的才允许被使用。否则在设备管理器下,安装完硬件驱动后设备上会有“黄色感叹号”标识,右键该设备属性提示:“Windows 无法验证此设备所…

nginx配置 请求静态文件时带上额外的响应头信息

注意:这种方式添加的额外信息会出现在响应头中。 例如在location{}中,try_files之前添加如下信息: add_header X-Extra-Header "Value"; add_header X-Forwarded-For $proxy_add_x_forwarded_for; …

Triumphcore FPGA调测试记录

FPGA采用Xilinx pynq Z2开发板。基于V2.5版本开发 OverView uart端口映射 BUG调试记录 2024.1.7 复位状态导致取指时序错误 错误波形: 正确波形 问题代码: 2024.1.9 clock_wizard设置输入时钟是输出时钟的2^n倍,输出时钟的占空比才…

算法训练营第四十二天|动态规划:01背包理论基础 416. 分割等和子集

目录 动态规划&#xff1a;01背包理论基础416. 分割等和子集 动态规划&#xff1a;01背包理论基础 文章链接&#xff1a;代码随想录 题目链接&#xff1a;卡码网&#xff1a;46. 携带研究材料 01背包问题 二维数组解法&#xff1a; #include <bits/stdc.h> using namesp…

jsonvue-mobile 联动方式说明。

目录 jsonvue-mobile的联动类型分为两种 一种是命令式的&#xff1a; 另一种是响应式的&#xff1a; 联动场景 场景一&#xff1a;某一个字段的值变化时&#xff0c;同步修改另一个字段的值 命令式&#xff1a; 响应式&#xff1a; 场景一演示效果GIF 场景二&#xff1…

【模块系列】STM32TCS3472

前言 手上正好有TCS3472模块&#xff0c;也正好想在加深一下自己对I2C协议的理解和应用&#xff0c;所以就写了这个代码库出来。参考的资料主要来源于TCS3472的数据手册&#xff0c;和arduino中MH_TCS3472库的宏定义&#xff0c;和函数名称&#xff0c;我就没有重新命名&#x…