变量的存放

首先我们应该知道内存中有栈和堆,那么变量应该存放在哪里呢,

  • 1、基本类型 —> 保存在内存中,因为这些类型在内存中分别占有固定大小的空间,通过按值来访问。基本类型一共有6种:Undefined、Null、Boolean、Number 、String和Symbol

  • 2、引用类型 —> 保存在内存中,因为这种值的大小不固定,因此不能把它们保存到栈内存中,但内存地址大小的固定的,因此保存在堆内存中,在栈内存中存放的只是该对象的访问地址。当查询引用类型的变量时, 先从栈中读取内存地址, 然后再通过地址找到堆中的值。对于这种,我们把它叫做按引用访问

变量与内存 - 图1

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

几个问题

问题1:

  1. var a = 20;
  2. var b = a;
  3. b = 30;
  4. // 这时a的值是多少?

问题2:

  1. var a = { name: '前端开发' }
  2. var b = a;
  3. b.name = '进阶';
  4. // 这时a.name的值是多少

问题3:

  1. var a = { name: '前端开发' }
  2. var b = a;
  3. a = null;
  4. // 这时b的值是多少

现在来解答一下,三个问题的答案分别是20‘进阶’{ name: '前端开发' }

  • 对于问题1,a、b都是基本类型,它们的值是存储在栈中的,a、b分别有各自独立的栈空间,所以修改了b的值以后,a的值并不会发生变化。 如果把存储数据的内存比作仓库,相当于a仓库中存储了20瓶可乐,然后b=a 相当于把a仓库中20瓶可乐拷贝一份,存到了隔壁b仓库,a和b实际是两个仓库,这时b仓库可乐变成了30瓶并不会影响到a仓库。

  • 对于问题2,a、b都是引用类型,栈内存中存放地址指向堆内存中的对象,引用类型的复制会为新的变量自动分配一个新的值保存在变量对象中,但只是引用类型的一个地址指针而已,实际指向的是同一个对象,所以修改b.name的值后,相应的a.name也就发生了改变。 引用数据类型变量保存的是实际值的访问地址,如果把实际保存数据的内存比作仓库,那这个访问地址就好比打开仓库的钥匙,b=a 相当于a拷贝了一把钥匙给b,这两把钥匙打开的是同一个仓库,b.name = ‘进阶’ 相当于b通过钥匙打开仓库并修改了里面的值,这时a在打开仓库看到的也是修改后的值了。

  • 对于问题3,首先要说明的是 null 是基本类型,a = null 只是把 a 存储在栈内存中地址改变成了基本类型 null,并不会影响堆内存中的对象,所以 b 的值不受影响。a = nul 只是相当于a手上的钥匙变成null了,但是 a 并没有通过钥匙对原来的仓库有过修改,所以b打开仓库一看,哦,还是原来的内容。

    内存空间管理

    JavaScript的内存生命周期是

  • 1、分配你所需要的内存

  • 2、使用分配到的内存(读、写)
  • 3、不需要时将其释放、归还

JavaScript有自动垃圾收集机制,最常用的是通过标记清除的算法来找到哪些对象是不再继续使用的,使用a = null其实仅仅只是做了一个释放引用的操作,让 a 原本对应的值失去引用,脱离执行环境,这个值会在下一次垃圾收集器执行操作时被找到并释放。

在局部作用域中,当函数执行完毕,局部变量也就没有存在的必要了,因此垃圾收集器很容易做出判断并回收。但是全局变量什么时候需要自动释放内存空间则很难判断,因此在开发中,需要尽量避免使用全局变量。

思考题

1.

  1. var a = {n: 1};
  2. var b = a;
  3. a.x = a = {n: 2};
  4. a.x // 这时 a.x 的值是多少
  5. b.x // 这时 b.x 的值是多少

image.png

这道题的关键在于

  • 1、优先级。.的优先级高于=,所以先执行a.x,堆内存中的{n: 1}就会变成{n: 1, x: undefined},改变之后相应的b.x也变化了,因为指向的是同一个对象。

  • 2、赋值操作是从右到左,所以先执行a = {n: 2}a的引用就被改变了,然后这个返回值又赋值给了a.x需要注意的是这时候a.x是第一步中的{n: 1, x: undefined}那个对象,其实就是b.x,相当于b.x = {n: 2}

变量与内存 - 图3

2.

ES6语法中的 const 声明一个只读的常量,那为什么下面可以修改const的值?

  1. const foo = {};
  2. // 为 foo 添加一个属性,可以成功
  3. foo.prop = 123;
  4. foo.prop // 123
  5. // 将 foo 指向另一个对象,就会报错
  6. foo = {}; // TypeError: "foo" is read-only


解答
const实际上保证的,并不是变量的值不得改动,而是
变量指向的那个内存地址所保存的数据不得改动**。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心。