「前端进阶」JS中的栈内存堆内存

你知道的越多,你不知道的越多
点赞再看,手留余香,与有荣焉javascript

引言

JS的内存空间分为栈(stack)、堆(heap)、池(通常也会归类为栈中)。前端

其中栈存放变量,堆存放复杂对象,池存放常量,因此也叫常量池。java

栈数据结构

栈是一种特殊的列表,栈内的元素只能经过列表的一端访问,这一端称为栈顶。 栈被称为是一种后入先出(LIFO,last-in-first-out)的数据结构。 因为栈具备后入先出的特色,因此任何不在栈顶的元素都没法访问。 为了获得栈底的元素,必须先拿掉上面的元素。git

在这里,为方便理解,经过类比乒乓球盒子来分析栈的存取方式。github

这种乒乓球的存放方式与栈中存取数据的方式一模一样。 处于盒子中最顶层的乒乓球 5,它必定是最后被放进去,但能够最早被使用。 而咱们想要使用底层的乒乓球 1,就必须将上面的 4 个乒乓球取出来,让乒乓球1处于盒子顶层。 这就是栈空间先进后出,后进先出的特色。数组

堆数据结构

堆是一种通过排序的树形数据结构,每一个结点都有一个值。 一般咱们所说的堆的数据结构,是指二叉堆。 堆的特色是根结点的值最小(或最大),且根结点的两个子树也是一个堆。 因为堆的这个特性,经常使用来实现优先队列,堆的存取是随意,这就如同咱们在图书馆的书架上取书, 虽然书的摆放是有顺序的,可是咱们想取任意一本时没必要像栈同样,先取出前面全部的书, 咱们只须要关心书的名字。微信

变量类型与内存的关系

基本数据类型

基本数据类型共有6种:markdown

  1. Sting
  2. Number
  3. Boolean
  4. null
  5. undefined
  6. Symbol

基本数据类型保存在栈内存中,由于基本数据类型占用空间小、大小固定,经过按值来访问,属于被频繁使用的数据。数据结构

为了更好的搞懂基本数据类型变量与栈内存,咱们结合如下例子与图解进行理解:闭包

let num1 = 1;
let num2 = 1;
复制代码

PS: 须要注意的是闭包中的基本数据类型变量不保存在栈内存中,而是保存在堆内存中。这个问题,咱们后文再说。

引用数据类型

Array,Function,Object...能够认为除了上文提到的基本数据类型之外,全部类型都是引用数据类型。

引用数据类型存储在堆内存中,由于引用数据类型占据空间大、大小不固定。 若是存储在栈中,将会影响程序运行的性能; 引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。 当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中得到实体

为了更好的搞懂变量对象与堆内存,咱们结合如下例子与图解进行理解。

// 基本数据类型-栈内存
let a1 = 0;
// 基本数据类型-栈内存
let a2 = 'this is string';
// 基本数据类型-栈内存
let a3 = null;

// 对象的指针存放在栈内存中,指针指向的对象存放在堆内存中
let b = { m: 20 };
// 数组的指针存放在栈内存中,指针指向的数组存放在堆内存中
let c = [1, 2, 3];
复制代码

所以当咱们要访问堆内存中的引用数据类型时,实际上咱们首先是从变量中获取了该对象的地址指针, 而后再从堆内存中取得咱们须要的数据。

从内存角度来看变量复制

基本数据类型的复制

let a = 20;
let b = a;
b = 30;
console.log(a); // 此时a的值是多少,是30?仍是20?
复制代码

答案是:20

在这个例子中,a、b 都是基本类型,它们的值是存储在栈内存中的,a、b 分别有各自独立的栈空间, 因此修改了 b 的值之后,a 的值并不会发生变化。

从下图能够清晰的看到变量是如何复制并修改的。

引用数据类型的复制

let m = { a: 10, b: 20 };
let n = m;
n.a = 15;
console.log(m.a) //此时m.a的值是多少,是10?仍是15?
复制代码

答案是:15

在这个例子中,m、n都是引用类型,栈内存中存放地址指向堆内存中的对象, 引用类型的复制会为新的变量自动分配一个新的值保存在变量中, 但只是引用类型的一个地址指针而已,实际指向的是同一个对象, 因此修改 n.a 的值后,相应的 m.a 也就发生了改变。

从下图能够清晰的看到变量是如何复制并修改的。

栈内存和堆内存的优缺点

在JS中,基本数据类型变量大小固定,而且操做简单容易,因此把它们放入栈中存储。 引用类型变量大小不固定,因此把它们分配给堆中,让他们申请空间的时候本身肯定大小,这样把它们分开存储可以使得程序运行起来占用的内存最小。

栈内存因为它的特色,因此它的系统效率较高。 堆内存须要分配空间和地址,还要把地址存到栈中,因此效率低于栈。

栈内存和堆内存的垃圾回收

栈内存中变量通常在它的当前执行环境结束就会被销毁被垃圾回收制回收, 而堆内存中的变量则不会,由于不肯定其余的地方是否是还有一些对它的引用。 堆内存中的变量只有在全部对它的引用都结束的时候才会被回收。

关于垃圾回收详细内容参见另外一篇文章(JS中的内存管理)

闭包与堆内存

闭包中的变量并不保存中栈内存中,而是保存在堆内存中。 这也就解释了函数调用以后以后为何闭包还能引用到函数内的变量。

咱们先来看什么是闭包:

function A() {
  let a = 1;
  function B() {
      console.log(a);
  }
  return B;
}
let res = A();
复制代码

函数 A 返回了一个函数 B,而且函数 B 中使用了函数 A 的变量,函数 B 就被称为闭包。

函数 A 弹出调用栈后,函数 A 中的变量这时候是存储在堆上的,因此函数B依旧能引用到函数A中的变量。 如今的 JS 引擎能够经过逃逸分析辨别出哪些变量须要存储在堆上,哪些须要存储在栈上。

系列文章推荐

参考

写在最后

  • 文中若有错误,欢迎在评论区指正,若是这篇文章帮到了你,欢迎点赞关注
  • 本文同步首发与github,可在github中找到更多精品文章,欢迎Watch & Star ★
  • 后续文章参见:计划

欢迎关注微信公众号【前端小黑屋】,每周1-3篇精品优质文章推送,助你走上进阶之旅

相关文章
相关标签/搜索