JS栈内存与堆内存
  1. var a = 20;
  2. var b = 'abc';
  3. var c = true;
  4. var d = { m: 20 }

因为JavaScript具有自动垃圾回收机制,所以对于前端开发来说,内存空间并不是一个经常被提及的概念,很容易被大家忽视。特别是很多不是计算机专业的朋友在进入到前端之后,会对内存空间的认知比较模糊,甚至有些人干脆就是一无所知。

当然也包括我自己。在很长一段时间里认为内存空间的概念在JS的学习中并不是那么重要。可是后我当我回过头来重新整理JS基础时,发现由于对它们的模糊认知,导致了很多东西我都理解得并不明白。比如最基本的引用数据类型和引用传递到底是怎么回事儿?比如浅复制与深复制有什么不同?还有闭包,原型等等。

因此后来我才渐渐明白,想要对JS的理解更加深刻,就必须对内存空间有一个清晰的认知。

在JS中,每一个数据都需要一个内存空间。内存空间又被分为两种,栈内存(stack)与堆内存(heap)。

基础数据类型与栈内存

JS中的基础数据类型,这些值都有固定的大小,往往都保存在栈内存中(闭包除外),由系统自动分配存储空间。我们可以直接操作保存在栈内存空间的值,因此基础数据类型都是按值访问。

数据在栈内存中的存储与使用方式类似于数据结构中的堆栈数据结构,遵循后进先出的原则。

基础数据类型: Number String Null Undefined Boolean
复习一下,此问题常常在面试中问到,然而答不出来的人大有人在 ~ ~

要简单理解栈内存空间的存储方式,我们可以通过类比乒乓球盒子来分析。如下图左侧。

这种乒乓球的存放方式与栈内存中存储数据的方式如出一辙。处于盒子中最顶层的乒乓球5,它一定是最后被放进去,但可以最先被使用。而我们想要使用底层的乒乓球1,就必须将上面的4个乒乓球取出来,让乒乓球1处于盒子顶层。这就是栈空间先进后出,后进先出的特点。图中已经详细的表明了栈空间的存储原理。

拓展阅读:原来你是这样的 setTimeout

引用数据类型与堆内存

与其他语言不通,JS的引用数据类型,比如数组Array,它们值的大小是不固定的。引用数据类型的值是保存在堆内存中的对象。JavaScript不允许直接访问堆内存中的位置,因此我们不能直接操作对象的堆内存空间。在操作对象时,实际上是在操作对象的引用而不是实际的对象。因此,引用类型的值都是按引用访问的。这里的引用,我们可以粗浅地理解为保存在栈内存中的一个地址,该地址与堆内存的实际值相关联。

要简单理解堆内存空间的存储方式,我们可以通过书架与书进行类比。

书虽然也有序的存放在书架上,但是我们只要知道书的名字,我们就可以很方便的取出我们想要的书,而不用像从乒乓球盒子里取乒乓一样,非得将上面的所有乒乓球拿出来才能取到中间的某一个乒乓球。好比在JSON格式的数据中,我们存储的key-value是可以无序的,因为顺序的不同并不影响我们的使用,我们只需要关心书的名字。

为了更好的搞懂栈内存与堆内存,我们可以结合以下例子与图解进行理解。

  1. var a1 = 0; // 栈
  2. var a2 = 'this is string'; // 栈
  3. var a3 = null; // 栈
  4. var b = { m: 20 }; // 变量b存在于栈中,{m: 20} 作为对象存在于堆内存中
  5. var c = [1, 2, 3]; // 变量c存在于栈中,[1, 2, 3] 作为对象存在于堆内存中

上例变量的内存分配情况图解

因此当我们要访问堆内存中的引用数据类型时,实际上我们首先是从栈中获取了该对象的地址引用(或者地址指针),然后再从堆内存中取得我们需要的数据。

理解了JS的内存空间,我们就可以借助内存空间的特性来验证一下引用类型的一些特点了。

在前端面试中我们常常会遇到这样一个类似的题目

  1. // demo01.js
  2. var a = 20;
  3. var b = a;
  4. b = 30;
  5. // 这时a的值是多少?
  1. // demo02.js
  2. var m = { a: 10, b: 20 }
  3. var n = m;
  4. n.a = 15;
  5. // 这时m.a的值是多少

在栈内存中的数据发生复制行为时,系统会自动为新的变量分配一个新值。var b = a执行之后,a与b虽然值都等于20,但是他们其实已经是相互独立互不影响的值了。具体如图。所以我们修改了b的值以后,a的值并不会发生变化。

demo01图解

在demo02中,我们通过var n = m执行一次复制引用类型的操作。引用类型的复制同样也会为新的变量自动分配一个新的值保存在栈内存中,但不同的是,这个新的值,仅仅只是引用类型的一个地址指针。当地址指针相同时,尽管他们相互独立,但是在堆内存中访问到的具体对象实际上是同一个。如图所示。

因此当我改变n时,m也发生了变化。这就是引用类型的特性。

demo02图解

通过内存的角度来理解,是不是感觉要轻松很多。除此之外,我们还可以以此为基础,一步一步的理解JavaScript的执行上下文,作用域链,闭包,原型链等重要概念。但是时间有限,就不做详细的解读了。

内存空间管理

因为JavaScript具有自动垃圾收集机制,所以我们在开发时好像不用关心内存的使用问题,内存的分配与回收都完全实现了自动管理。但是根据我自己的开发经验,了解内存机制有助于自己清晰的认识到自己写的代码在执行过程中发生过什么,从而写出性能更加优秀的代码。因此关心内存是一件非常重要的事情。

JavaScript的内存生命周期

  1. 1. 分配你所需要的内存
  2. 2. 使用分配到的内存(读、写)
  3. 3. 不需要时将其释放、归还

为了便于理解,我们使用一个简单的例子来解释这个周期。

  1. var a = 20; // 在堆内存中给数值变量分配空间
  2. alert(a + 100); // 使用内存
  3. var a = null; // 使用完毕之后,释放内存空间

第一步和第二步我们都很好理解,JavaScript在定义变量时就完成了内存分配。第三步释放内存空间则是我们需要重点理解的一个点。

JavaScript有自动垃圾收集机制,那么这个自动垃圾收集机制的原理是什么呢?其实很简单,就是找出那些不再继续使用的值,然后释放其占用的内存。垃圾收集器会每隔固定的时间段就执行一次释放操作。

在JavaScript中,最常用的是通过标记清除的算法来找到哪些对象是不再继续使用的,因此a = null其实仅仅只是做了一个释放引用的操作,让 a 原本对应的值失去引用,脱离执行环境,这个值会在下一次垃圾收集器执行操作时被找到并释放。而在适当的时候解除引用,是为页面获得更好性能的一个重要方式。

  • 在局部作用域中,当函数执行完毕,局部变量也就没有存在的必要了,因此垃圾收集器很容易做出判断并回收。但是全局变量什么时候需要自动释放内存空间则很难判断,因此在我们的开发中,需要尽量避免使用全局变量,以确保性能问题。
  • 要详细了解垃圾收集机制,建议阅读《JavaScript高级编程》中的4.3节

最后总结一下:

栈内存 堆内存
存储基础数据类型 存储引用数据类型/或基础数据类型,如闭包中的数据
按值访问 按引用访问
存储的值大小固定 存储的值大小不定,可动态调整
由系统自动分配内存空间 由程序员通过代码进行分配
主要用来执行程序 主要用来存放对象
空间小,运行效率高 空间大,但是运行效率相对较低
先进后出,后进先出 无序存储,可根据引用直接获取

干货 | JavaScript内存空间详解的更多相关文章

  1. String类内存空间详解

    java.lang.String类内存问题详解 字符串理解的难点在于其在堆内存空间上的特殊性,字符串String对象在堆内存上有两种空间: 字符串池(String pool):特殊的堆内存,专门存放S ...

  2. java内存空间详解

    Java内存分配与管理是Java的核心技术之一,之前我们曾介绍过Java的内存管理与内存泄露以及Java垃圾回收方面的知识,今天我们再次深入Java核心,详细介绍一下Java在内存分配方面的知识.一般 ...

  3. 从mixin到new和prototype:Javascript原型机制详解

    从mixin到new和prototype:Javascript原型机制详解   这是一篇markdown格式的文章,更好的阅读体验请访问我的github,移动端请访问我的博客 继承是为了实现方法的复用 ...

  4. C语言内存对齐详解(2)

    接上一篇:C语言内存对齐详解(1) VC对结构的存储的特殊处理确实提高CPU存储变量的速度,但是有时候也带来了一些麻烦,我们也屏蔽掉变量默认的对齐方式,自己可以设定变量的对齐方式.VC 中提供了#pr ...

  5. Tomcat内存设置详解

    Java内存溢出详解 一.常见的Java内存溢出有以下三种: 1. java.lang.OutOfMemoryError: Java heap space ----JVM Heap(堆)溢出 JVM在 ...

  6. javascript 节点属性详解

    javascript 节点属性详解 根据 DOM,html 文档中的每个成分都是一个节点 DOM 是这样规定的:整个文档是一个文档节点每个 html 标签是一个元素节点包含在于 html 元素中的文本 ...

  7. Tomcat内存溢出详解【转载】

    本文转载自 http://elf8848.iteye.com/blog/378805 Java内存溢出详解 一.常见的Java内存溢出有以下三种: 1. java.lang.OutOfMemoryEr ...

  8. Linux 内存机制详解宝典

    Linux 内存机制详解宝典 在linux的内存分配机制中,优先使用物理内存,当物理内存还有空闲时(还够用),不会释放其占用内存,就算占用内存的程序已经被关闭了,该程序所占用的内存用来做缓存使用,对于 ...

  9. (转)Tomcat内存设置详解

    Java内存溢出详解 一.常见的Java内存溢出有以下三种: 1. java.lang.OutOfMemoryError: Java heap space ----JVM Heap(堆)溢出JVM在启 ...

随机推荐

  1. private static final Logger logger= LoggerFactory.getLogger(WhMainBusi.class);

    LoggerFactory.getLogger(WhMainBusi.class):指定类初始化日志对象,在日志输出的时候,将会打印日志信息所在的类.如: logger.info("日志信息 ...

  2. fatal error: vector: No such file or directory

    jni/mac.cpp:28:18: fatal error: vector: No such file or directory jni/mac.cpp:29:18: fatal error: me ...

  3. AFNetWorking上传JSON串

    AFHTTPSessionManager *manager = [AFHTTPSessionManager manager]; manager.responseSerializer = [AFJSON ...

  4. Mac OSX上卸载Anaconda

    方案一 anaconda安装程序在~/.bash_profile脚本中新添加了一行,将anaconda bin目录添加到了$PATH环境变量中.所以你只需要删除anaconda目录,但是最好也从安装脚 ...

  5. 如何用python发邮件

    python发送各类邮件的主要方法 一.相关模块介绍 发送邮件主要用到了smtplib和email两个模块,这里首先就两个模块进行一下简单的介绍:     1.smtplib模块 smtplib.SM ...

  6. wxPython:消息对话框MessageDialog

    wxMessageDialog(wxWindow* parent, const wxString& message, const wxString& caption = "M ...

  7. clone git 项目到 非空目录

    如果我们往一个非空的目录下 clone git 项目,就会提示错误信息: fatal: destination path '.' already exists and is not an empty ...

  8. tcp_nopush高性能

    nginx sendfile tcp_nopush tcp_nodelay参数解释

  9. 要求根据RandomStr.java:使用类型转换生成六位验证字符串,示例程序每次运 行时,都会生成不同的字符串。

    1.程序设计思想验证码 ①定义一个字符串变量来保存随机生成的. ②利用循环产生六位随机数,在产生每一位时将其转换为char类型并写在字符串后面. ③利用对话框显示生成的验证码,并提示用户输入验证码. ...

  10. 1021 Deepest Root

    这道题的关键在于如何求两个最远的结点,二叉树比较容易一直DFS就能找到,但是普通树就比较麻烦.要先找到一端,再去找另外一端,两端的并集就是答案.因为结点都是对称的,所以两端都是答案.还要注意去重,12 ...