头图

一、栈

1、数据结构
栈是一种先进后出的数据结构,栈内存是内存中用于存放临时变量的一片内存块。它是一种特殊的列表,栈内的元素只能通过列表的一端访问,这一端称为栈顶。 栈被称为是一种后入先出(LIFO,last-in-first-out)的数据结构。 由于栈具有后入先出的特点,所以任何不在栈顶的元素都无法访问。 为了得到栈底的元素,必须先拿掉上面的元素。

2、数据存储
为方便大家理解,这里我们通过类比乒乓球盒子来分析栈的存取方式。这种乒乓球的存放方式与栈中存取数据的方式如出一辙。 处于顶层的乒乓球5号,它一定是最后被放进去的,但可以最先被拿来使用。 而我们想要使用底层的乒乓球1号,就必须将上面的 4 个乒乓球取出来,让乒乓球1号处于盒子顶层。 这就是栈空间先进后出,后进先出的特点。

一般来说,String、Number、Boolean、null、undefined、Symbol这些基本数据类型保存在栈内存中,因为基本数据类型占用空间小、大小固定,通过按值来访问,属于被频繁使用的数据。
3、举例说明

let a = 20;
let b = a;
b = 30;
console.log(a); // 此时a的值是多少,是30?还是20?

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

4、执行与回收
在JS中,基本数据类型变量大小固定,并且操作简单容易,所以把它们放入栈中存储。一般来说栈内存是线性有序存储,容量小,系统分配效率高。
栈内存中变量一般在它的当前执行环境结束就会被销毁被垃圾回收制回收。

二、堆

1、数据结构
堆是一种经过排序的树形数据结构,每个结点都有一个值。 通常我们所说的堆的数据结构,是指二叉堆。 堆的特点是根结点的值最小(或最大),且根结点的两个子树也是一个堆。 由于堆的这个特性,常用来实现优先队列,堆的存取是随意,这就如同我们在图书馆的书架上取书, 虽然书的摆放是有顺序的,但是我们想取任意一本时不必像栈一样,先取出前面所有的书, 我们只需要关心书的名字。
2、数据存储
Array,Function,Object…可以认为除了上文提到的基本数据类型外的引用数据类型。引用数据类型存储在堆内存中,因为引用数据类型占据空间大、大小不固定。 如果存储在栈中,将会影响程序运行的性能;引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。 当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。为了更好的搞懂变量对象与堆内存,我们结合以下例子与图解进行理解。

// 基本数据类型-栈内存
let a1 = 0;
// 基本数据类型-栈内存
let a2 = 'this is string';
// 基本数据类型-栈内存
let a3 = null;
// 对象的指针存放在栈内存中,指针指向的对象存放在堆内存中
let b = { m: 20 };
// 数组的指针存放在栈内存中,指针指向的数组存放在堆内存中
let c = [1, 2, 3];


3、举例说明
当我们要访问堆内存中的引用数据类型时,实际上我们首先是从变量中获取了该对象的地址指针, 然后再从堆内存中取得我们需要的数据。

let m = { a: 10, b: 20 };
let n = m;
n.a = 16;
console.log(m.a) //此时m.a的值是多少,是10?还是16?

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

4、执行与回收
引用类型变量大小不固定,所以把它们分配给堆中,首先要在堆内存新分配存储区域,之后又要把指针(地址值)存储到栈内存中,效率相对就要低一些了。
而推内存中的变量因为存在很多不确定的引用,只有当所有指向堆内存的指针全部销毁之后才会被垃圾回收。
所以,使用匿名函数当调用完函数之后把储存在栈内存中的指针赋值为null也是一种优化性能防止内存泄漏的一种方式。


high-profile
29 声望2 粉丝