一、栈
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也是一种 优化性能 避免内存透露的一种形式。