美文网首页
JavaScript进阶-内存空间详解(双十一过后的一更)

JavaScript进阶-内存空间详解(双十一过后的一更)

作者: LinDaiDai_霖呆呆 | 来源:发表于2019-12-08 13:13 被阅读0次

    前言

    本章继《JavaScript进阶-执行上下文栈和变量对象(一周一更)》
    之后继续深入学习JS的基础知识.

    上面我们已经介绍了很多关于JS中执行上下文以及变量对象的知识, 而现在我要讲解的是JS的内存空间.

    这一章你会学习到:

    • 三种数据结构: 堆(heap)、栈(stack)、队列(queue)

    • 变量的存放

    • 内存空间管理

    三种数据结构

    JS中三种重要的数据结构, 如图:

    img1

    (图片来源前端九五六-Javascript 内存空间管理)

    栈数据结构

    其实在《JavaScript执行上下文》中我就已经提到了执行栈, 让我们一起来回顾一下:

    栈的特点: 后进先出(LIFO)的结构.

    LIFO: last-in, first-out,类似于向乒乓球桶中放球,最先放入的球最后取出)

    这里还是贴上一张网图方便大家理解的好:

    img2

    栈中的数据就像是一个个乒乓球, 最先进去的最后出来.

    注⚠️

    这里所说的进栈出栈不是指赋值算进, 使用算出. 而是指赋值算进, 被清理算出, 而且位于同一函数作用域下的变量, 应该是在栈的同一层.

    所谓的变量存储于栈内存中的栈,传统意义上说指的是由内存自动创建分配的空间,例如函数的参数值与局部变量,只是其操作方式类似于栈操作,所以叫栈内存。

    比如函数调用其实就相当于栈的形式:

    例子🌰:

    function fn1() {
        console.log(1)
      fn2()
    }
    function fn2() {
        console.log(2)
        fn3()
    }
    function fn3() {
        console.log(3)
    }
    fn1()
    

    如上, 声明的顺序是1, 2, 3 , 但是释放的顺序是为3, 2, 1 .

    这里释放按照这个顺序是因为 3最先执行完, 所以最先被释放.

    堆数据结构

    一种树状结构。好比 JSON 格式中的数据,你有 key,我有对应的 value, 就立马返给你。

    因为我们知道JSON格式的存储是无序的, 所以没有先后顺序, 所以它是一种绝对公平的数据结构.

    如图所示:

    image.png

    队列数据结构

    队列数据结构不同于堆, 队列是一种先进先出(FIFO) 的数据结构.

    它也是事件循环(Event Loop) 的基础结构.

    如图所示:

    image.png

    最先进入队列的任务最先出来, 类似于你排队买票, 排在前面的人先买.

    变量的存放

    通过上面的介绍我们知道了, 内存中有堆了栈, 那么JS变量具体是存放在哪里呢?

    • 基本数据类型保存在内存中;
    • 引用数据类型保存在内存中.
    1. 基本数据类型6种: Undefined、Null、Boolean、Number、String、Symbol, 由于他们在内存中分别占有固定大小的空间, 通过按值来访问.
    2. 引用数据类型: 也就是Object对象, 它的存储分为访问地址实际存放的地方; 访问地址是存储在中的, 当查询引用类型变量的时候, 会先从中读取内存地址(也就是访问地址), 然后再通过地址找到中的值.因此, 这种我们也把它叫为引用访问.

    一张图方便你理解🤔

    image.png

    在计算机的数据结构中,栈比堆的运算速度快,Object是一个复杂的结构且可以扩展:数组可扩充,对象可添加属性,都可以增删改查。将他们放在堆中是为了不影响栈的效率。而是通过引用的方式查找到堆中的实际对象再进行操作。所以查找引用类型值的时候先去查找再去查找。

    变量存放案例

    要是你读完了上面的堆栈存储介绍还有点模糊的话, 我们不妨来看几个案例.

    案例一🌰:

    var a = 1;
    var b = a;
    b = 2;
    console.log(a); // a = ?
    

    案例二🌰:

    var obj1 = { a: 1, b: 2 };
    var obj2 = obj1;
    obj2.a = 3;
    console.log(obj1.a); // obj1.a = ?
    

    案例三🌰:

    var obj1 = { a: 1 };
    var obj2 = obj1;
    obj1 = null;
    console.log(obj2); // obj2 = ?
    

    上面三个案例的答案分别对应的是: 1、3、{ a: 1 }.

    • 案例一中, a和b都是基本数据类型, 它们的值分别存储在各自独立的栈空间中, 是互不影响的, 所以修改了b的值后a还是不变. var b = a 的操作, 你可以理解为单纯的b赋值了值1, 而后a和b没有任何关系了.
    • 案例二中, 创建obj1的时候, 在栈中存储了一个名为obj1的变量, 同时开辟了一个堆内存用于存放了{a: 1, b: 2}对象, obj1中存放的就是指向这个堆内存对象的地址. 因此obj2进行赋值的时候拷贝的只是obj1中的地址, 实际上它们指向的都是堆内存的对象.在第三步改变这个对象的值的时候, 也相当于同时改变了obj1.
    • 案例三中, 开始时, obj1obj2指向的都是同一堆内存对象{a: 1}, 在第三步将obj1赋值为null仅仅只是改变了栈中obj1的内存地址,将它变为了基本数据类型null, 并不会影响堆内存对象. 同样的, 你要将obj1不赋值为null, 而是赋值为{b: 2}, 对obj2也还是没有影响.

    内存空间管理

    在上面我们说了那么多的栈内存, 堆内存, 那么在JS中, 是怎样管理这些内存空间的呢?

    首先, 同样的, 内存空间也是有属于自己的生命周期, 它主要分为三个阶段:

    1. 分配你所需的内存;
    2. 使用分配到的内存(读、写);
    3. 不需要的时候将其释放、归还.

    我们可以用个例子来看一下看.

    案例一🌰:

    var a = 1; // 在内存中给数值变量分配空间
    alart(a + 2); // 使用内存
    a = null; // 使用完后, 释放内存空间
    

    上面三步分别对应着三个阶段. 当然, a = null这个操作是我们手动将a的内存空间释放. 若没有这个过程, JS会自己帮我做一些释放内存的工作吗? 答案当然是肯定的.

    JS有自动垃圾收集机制, 听着这个机制的名字我想大家就知道它是做什么的了, 没错就是字面意思, 它会找出那些不再继续使用的值,然后释放其占用的内存。垃圾收集器会每隔固定的时间段就执行一次释放操作。

    在自动垃圾收集机制中, 最常用的就是通过标记清除的算法来找到哪些对象不再继续使用. 其实上面我说的将a = null手动释放内存其实是不准确的. 因为使用a = null仅仅只是做了一个释放引用的操作, 让a原本对应的值失去引用, 脱离执行环境, 这个值会在下一次垃圾收集器执行操作的时候被找到并释放.

    还有一点, 在局部作用域中, 当函数执行完毕了之后, 局部变量就没有存在下去的必要了, 此时垃圾收集器知道这类变量是需要回收的, 所以很容易判断.

    但是全局变量什么时候需要释放内存空间则很难判断,因此在我们的开发中,原则上应该避免使用全局变量。

    后语

    掌握好JS中的内存空间的基础知识, 才能避免在实际开发中产生的一系列性能问题.

    参考文章:

    木易杨前端进阶-JavaScript深入之内存空间详细图解

    前端基础进阶(一):内存空间详细图解

    前端九五六-Javascript 内存空间管理

    关于js中 “栈空间的先进后出,后进先出” 的疑问?

    知识无价, 支持原创

    相关文章

      网友评论

          本文标题:JavaScript进阶-内存空间详解(双十一过后的一更)

          本文链接:https://www.haomeiwen.com/subject/gbrmgctx.html