zoukankan      html  css  js  c++  java
  • JavaScript的内存

    基本数据类型的内存大小都是固定的(采用静态内存分配),而引用数据类型的内存大小都是动态不固定的(采用动态内存分配),可能会随时发生变化。因此在内存分配阶段这两种数据类型会有一定的差异。

    静态内存分配和动态内存分配的区别如下表所示:

    静态内存分配 动态内存分配
    编译阶段可确定大小 编译阶段无法确定大小
    在编译时执行 在运行时执行
    分配给堆栈 分配给堆
    顺序分配,后进先出(LIFO) 无序分配

     

    JavaScript中的内存分配

    使用基本数据类型和引用数据类型来分别展示内存的分配过程,可以理解JavaScript的底层细节。

    首先我们从一个简单的基本数据类型的赋值开始,代码如下:

    let num = 1;

    当JavaScript引擎在执行到这行代码时,会执行如下操作:

    1 为变量创建一个唯一标识符(identifier) --- num,该标识符用于与栈内存中的地址A1形成映射关系。

    2 在栈内存中为其分配一个地址A1

    3 将值1存储到分配的地址。

    示例图如下:

    通常我们说num变量的值等于1,但其实严格意义上来讲,num变量的值等于栈内存中存放对应值的内存地址(如图中的A1)。

    接下来我们创建一个新的变量newNum并将num赋值给它:

    let newNum = num;

    经过以上赋值之后,通常说newNum的值等于num的值为1,同样从严格意义上来讲的话是指newNumnum指向同一个内存地址A1,如下图所示:

    如果接下来我们执行以下操作,看会发生什么:

    num = num + 1;

    我们对num变量进行自增长,很显然num变量的值为2。由于newNumnum指向同一个内存地址A1,那么此时newNum的值是否也为2呢,在回答这个问题之前,我们先来看一下当前内存地址发生的变化:

    在上图中我们可以发现,num变量的内存地址发生了改变,由原来的A1变为A2,这是因为在JS中的基本数据类型都是不可变的,一旦修改,只会为其分配新的内存地址并将修改后的新值存入到新的地址中,因此回答上面的那个问题,newNum的值保持不变,依旧为1,因为它的内存地址没有发生改变。

    再看如下示例:

    let str = 'ab';
    str = str + 'c';

    因为字符串也是属于基本数据类型,基本数据类型都是不可变的,所以即使上述代码中只是简单的将c拼接到了原来的字符串ab后面,但是依旧会为其分配新的内存地址,变量str最终会指向这个新的内存地址,如下图所示:

    了解了基本数据类型的内存分配方式之后,接下来我们来了解下引用数据类型的内存分配方式。同样我们从一个简单的引用数据类型的赋值开始:

    let arr = [];

    当JavaScript引擎在执行到这行代码时,会执行如下操作:

    1 为变量创建一个唯一标识符(identifier)---arr,该标识符用于与栈内存中的地址A3形成映射关系。

    2 在栈内存中为其分配一个地址A3

    3 在栈内存中存储堆中分配的内存地址的值H1

    4 在堆中存储分配的值空数组[]

    示例图如下:

     

    在JavaScript引擎(例如Chrome和Node的V8引擎)中主要是由两个部件组成,一个叫内存堆(Memory Heap),一个叫调用堆栈(Call Stack)。其中调用堆栈除了函数调用之外,主要用于存放基本数据类型的值,而引用数据类型的值一般都存放在内存堆中,堆中存放的数据都是无序的并且可以动态地增长,所以非常适合用于存储数组和对象。

    let和const的对比

    在了解完以上两种数据类型的内存分配方式后,我们这里对letconst的使用方式进行一下对比,通常来说,我们建议在写代码的过程中能使用const的地方尽量减少使用let,这样可以在某种程度上避免变量被无端修改而引发的一系列问题。如下代码:

    let num = 1;
    num = num + 1;
    let arr = [];
    arr.push(1);
    arr.push(2);
    arr.push(3);

    在上述代码中,变量num因为使用let的方式声明,所以允许其被修改,因为基本类型的值是不可变的,所以会为num变量分配新的内存地址。对于arr变量,这里同样使用let方式进行声明,表示允许其修改,但是对于push操作其实并没有修改arr变量的内存地址,只是将新的值推入了堆内存的数组中,所以此处建议修改为使用const进行声明。

    如下示例:

    const num = 1;
    num = num + 1;

    由在上边了解到的基本数据类型的内存分配方式,我们知道为变量num在栈内存中分配了一个地址来保存对应的值。

    但是这里我们是使用const的方式来进行声明的,当我们重新为变量num进行赋值时,JS尝试为其分配新的内存地址,那么这里也就是抛出错误的地方,因为我们明确不允许对其进行修改。

    因此在控制台中我们会看到对应的报错信息。

    再看如下示例:

    const arr = [];

    对于引用数据类型,我们知道会在栈内存上为其分配内存地址,存储的是堆中的内存地址的值。

     我们做如下操作:

    arr.push(1);
    arr.push(2);
    arr.push(3);

     执行push操作实际上是将新值推入堆中的数组,内存地址并没有发生改变。这也就是为什么虽然使用const声明变量,但是依旧没有报错的原因。但是如果我们使用如下方式:

    arr = 1;
    arr = undefined;
    arr = null;
    arr = [];
    arr = {};

    这些方式都会修改原数组的内存地址,const声明是不允许修改内存地址的,所以很明显会抛出错误。因此这里也是建议默认情况下使用const声明变量,除非需要修改内存地址,const声明的变量必须在声明时进行初始化,也方便了其他前端人员能一眼看出哪些变量是不可变的。

    原文: https://www.cnblogs.com/tangshiwei/p/12020478.html

  • 相关阅读:
    BZOJ 1013: [JSOI2008]球形空间产生器sphere
    BZOJ 1012: [JSOI2008]最大数maxnumber
    BZOJ 1011: [HNOI2008]遥远的行星
    BZOJ 1008: [HNOI2008]越狱
    BZOJ 1007: [HNOI2008]水平可见直线
    BZOJ 1003: [ZJOI2006]物流运输
    Spark core 总结
    SparkRDD算子(transformations算子和actions算子)
    SparkRDD算子初识
    初识Spark
  • 原文地址:https://www.cnblogs.com/xjy20170907/p/12021741.html
Copyright © 2011-2022 走看看