内存中的堆和栈怎么理解,如何对 JavaScript 算法产生的影响(二)

数据结构的栈和堆

首先在数据结构上要知道堆栈,尽管我们这么称呼它,但实际上堆栈是两种数据结构:堆和栈。堆和栈都是一种数据项按序排列的数据结构。

栈方法(数据结构):

JavaScript的数组模拟栈行为,使用的是一种可以限制你插入和删除项的数据结构。
栈就是其一,顾名思义,栈道,栅栏,就是一个一个安装,后面安装的先拆除(如果需要拆除)修理。
而栈中项的插入(推入)和移除(弹出),只发生在一个位置---栈的顶部。使用JavaScript的push()和pop()方法,以便实现类似栈的行为。


堆方法(数据结构):

    □:而堆就不同了,堆是一种经过排序的树形数据结构,每个结点都有一个值。
    □:通常我们所说的堆的数据结构,是指二叉堆。
    □:堆的特点是根结点的值最小(或最大),且根结点的两个子树也是一个堆。

由于堆的这个特性,常用来实现优先队列,堆的存取是随意,这就如同我们在图书馆的书架上取书,
虽然书的摆放是有顺序的,但是我们想取任意一本时不必像栈一样,先取出前面所有的书,书架这种机制不同于箱子,我们可以直接取出我们想要的书。

内存中分配堆和栈

堆栈的空间分配:

    □:栈(操作系统):由操作系统自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
    □:堆(操作系统): 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收,分配方式倒是类似于链表。

堆栈的缓存方式:

    □:栈使用的是一级缓存, 他们通常都是被调用时处于存储空间中,调用完毕立即释放。
    □:堆则是存放在二级缓存中,生命周期由虚拟机的垃圾回收算法来决定(并不是一旦成为孤儿对象就能被回收)。所以调用这些对象的速度要相对来得低一些。

js中的堆内存和栈内存:

    □:JavaScript中栈内存用来存储各种基本类型的变量,包括Boolean、Number、String、Undefined、Null...以及对象变量的指针,这时候栈内存给人的感觉就像一个线性排列的空间,每个小单元大小基本相等,栈内存中的变量一般都是已知大小或者有范围上限的,算作一种简单存储。
    □:JavaScript中堆内存主要负责像对象Object这种变量类型的存储,堆内存存储的对象类型数据对于大小这方面,一般都是未知的,(所以这大概也是为什么null作为一个object类型的变量却存储在栈内存中的原因)。

感受一下JavaScript中的堆和栈



测试基本数据类型:

var a = 1;
var b = 1;
console.log(a === b);//true
var c = "水电费";
var d = "水电费";
console.log(c === d);//true

基本数据类型,因为都是存在栈内存中的,以上面的int为例:
var a = 1;变量 a 存在栈内存中,他的值是基本数据类型(int),自然也是在栈内存中,栈内存有没有1?没有那就拿出一块内存存1,这个变量a指向这块值为1的栈内存地址;
var b = 1;同理,变量 b 也是在栈内存中的,但是赋值的时候,发现,栈内存有一块地址存着int型的值1,那么就直接指向这块栈内存了;
所以最终 a === b 是 true;

测试引用数据类型:
 

var a = new Number(10);
var b = new Number(10);
console.log(a === b);//false


new关键字生成的对象都是存在于堆内存中的,上述代码中:
var a = new Number(10);变量 a 存在于栈内存中,他的值是一个指针,这个指针指向堆内存中的一个对象!b的值也是一个指针,指向的是堆内存中的这个对象。
所以两个指针不相等。a和b在栈内存中也开辟了空间,各自不相关。

C语言解释:

int a = 20;//实际变量的生命
int * ip;//指针变量的声明
ip = &a;//在指针变量中存储着 变量a 的地址

所以我们应该这么理解:
    □:普通变量的值类型是基本数据类型,指向栈内存中的一块地址;
    □:引用类型变量的值是指针,指向堆内存中的一块地址。

指针的赋值:

/* 指针赋值 */
var a = new String('桔子桑');
var b = a;
console.log(a === b);//true

我们看到,指针型变量 a 的值是一个指针,指向堆内存中一块地址;然后 又定义了一个变量 b ,他的值等于 a,a 的值是什么?a 的值是一个指针啊,那么变量 b 就等于这个指针,自然也是指向堆内存的那一块地址咯。



const关键字:

我们知道const关键字用来定义一个常量,const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址不得改动。

对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个栈内存地址,因此等同于常量。

但对于复合类型的数据(主要是对象和数组),变量指向的栈内存地址,保存的只是一个栈内存指针,const只能保证这个指针是固定的,至于它指向的数据结构是不是可变的,就完全不能控制了。

所以,const保证的是栈内存这边变量与其值不能发生改变,所以对于复合类型,指针指向的堆内存中存的数据结构能否改变是左右不了的。

/*const关键字*/
const a = 1;
a = 2;
console.log(a);
const b = {
    name : "Jerry"
}
b.name = "Tom";

对于前者,因为常量 a 和他的值都是在栈内存中的,不能更改,手动修改只会报错:


而对于后者,因为常量 b 的值是一个指针,这个指针由于存在于栈内存,所以他的指向是不能更改的(堆内存地址),但是对应堆内存地址中存的数据结构(存了一个对象)是可以更改的。