JavaScript里面的堆和栈,以及堆常见的问题(深拷贝和浅拷贝)

101 阅读4分钟

堆的话,在JavaScript里面是堆内存的简称;

栈的话,在JavaScript里面的是栈内存的简称;

一、栈的属性

特点:先进后出,栈(Stack),具有后进先出的特点(LIFO,last in first out),是一种高效的列表,只对栈顶的数据进行添加和删除,线性结构 就好像好多书叠在一起,每次都是取最上边的,每次也都会把书放到最上边去。对栈的操作主要是压栈push和出栈并删除pop,还有peek方法,找到栈顶数据,并返回这个数据。当然,栈还会有其他一些基本的操作如empty属性、clear方法、length等。栈除了适合用于记录数值和地址以外,还适合协助数制间的转换,如二进制转换为八进制等。还可以模拟递归和处理回文(回文是正着排列的文字和倒着排列的文字都是一样的一种形式)。

下面我们用JavaScript来模拟实现一下

 function Stack() {
            this.items = []
            Stack.prototype.push = function(ele) {
                this.items.push(ele)
            }
            Stack.prototype.pop = function() {
                return this.items.pop()
            }
            Stack.prototype.isEmpty = function() {
                return this.items.length === 0
            }
            Stack.prototype.len = function() {
                return this.items.length
            }
        }
        let stack = new Stack()
        stack.push(1)
        stack.push(1)
        stack.push(1)
        stack.pop()
        console.log(stack);
        console.log(stack.isEmpty());

二、队列

队列(Queue),具有先进先出(FIFO,first in first out)的特点,是只能在队首取出或者删除元素,在队尾插入元素的列表。 队列的用处很大,除了前边说的堆内存以外(堆内存中会存在队列的数据结构,当然,堆中还会有其他结构如树等),还有消息队列,还可以执行提交操作系统的一系列进程的任务,打印任务池,模拟银行或超市排队的顾客等。对队列的操作主要是入队push(enQueue过程)和出队shift(deQueue过程),还有front(读取队首数据)和back(读取队尾数据)。另外,在计算机早期,打孔纸的年代,还有一种基数排序法是使用队列实现的,虽然不怎么高效,但是很好玩。这个算法需要九个队列,每个对应一个数字。将所有队列保存在一个数组中,使用取余和除法操作决定个位和十位。算法的剩余部分将数字加入相应的队列,根据个位数值对其重新排序,然后再根据十位上的数值进行排序,结果即为排好序的数字。

说到堆栈,我们讲的就是内存的使用和分配了,没有寄存器的事,也没有硬盘的事。

三、堆

各种语言在处理堆栈的原理上都大同小异。堆是动态分配内存,内存大小不一,也不会自动释放。栈是自动分配相对固定大小的内存空间,并由系统自动释放。

javascript的基本类型就5种:Undefined、Null、Boolean、Number和String,它们都是直接按值存储在栈中的,每种类型的数据占用的内存空间的大小是确定的,并由系统自动分配和自动释放。这样带来的好处就是,内存可以及时得到回收,相对于堆来说,更加容易管理内存空间。

javascript中其他类型的数据被称为引用类型的数据 : 如对象(Object)、数组(Array)、函数(Function) …,它们是通过拷贝和new出来的,这样的数据存储于堆中。其实,说存储于堆中,也不太准确,因为,引用类型的数据的地址指针是存储于栈中的,当我们想要访问引用类型的值的时候,需要先从栈中获得对象的地址指针,然后,在通过地址指针找到堆中的所需要的数据。

还有一个常见的问题,那就是浅拷贝和深拷贝, 浅拷贝只是将内存地址赋值给了另一个变量,

比如

const obj={
   name:'zb',
   age:10,
   obj2:{
      name1:'zhaobo'
   }
}

let newObj = obj

此时会发现当我们改变newObj中name 的值的时候,obj里面也发生了改变。因为这种赋值只是浅层拷贝,只是将obj的指针地址给赋值了过去。

遇到这种情况,我们需要深拷贝,

function isObject(value) {
    const valueType = typeof value
    return (value !== null) && (valueType === 'object' || valueType === 'function')
}

function deepCopy(originValue) {
    if (!isObject(originValue)) {
        return originValue
    }
     // 这里需要判断传入的数组还是对象
    const newObject = Array.isArray(originValue) ? [] : {}
    for (let key in originValue) {
        newObject[key] = deepCopy(originValue[key])
    }
    return newObject
}
const obj = {
    name: 'zb',
    age: 10,
    obj2: {
        name1: 'zhaobo'
    },
    sss: [1, 2, 3]
}

let newObj = deepCopy(obj)
newObj.obj2.name1 = 'kkkk'
console.log(obj);
console.log(newObj);