看一个例子:
1
2
3
4
5
6
7
8
|
var a =3; var b = a; a=1; console.log(a,b); var arr1 = [1,2,3,4]; var arr2 = arr1; arr1.push(5); console.log(arr2); |
第一个console.log(a,b) 1 3;
第二个console.log(arr2) [1,2,3,4,5]
这个问题很奇怪。为什么 b的值变成了 a 原来的值,没有随着a的重新赋值而变化,而 arr2却随着 arr1的变化而变化呢?
原因是;JS值分为两种 :原始值 和 引用值;
原始值:String,Undefined、Null、Boolean、Number
引用值:Object Array Function Date RegExp
所以很显然了,a 和 b 是属于Number类型的,即原始值;而 arr1 和 arr2是属于 Array 类型的,即引用值;
原始值存储在 栈 中;
引用值存储在 堆 中;并且 在 栈 中存储了 堆 的地址;如下图所示:
所以,两种值的存储方式不同,导致了问题的发生。
原始类型中:
1
2
3
4
|
var a =3; //在栈中 开辟空间存放3 并且名称为a var b = a; // 在栈中 开辟空间存放复制 a 的值 并且名称为b a=1; //在栈中 开辟空间存放3 并且名称为a,原来的地址 a 改为默认的地址,这里是默认的1008,如上图所示; console.log(a,b); |
引用类型中:
1
2
3
4
|
var arr1 = [1,2,3,4]; //在堆中 开辟空间存放[1,2,3,4] 并在栈中开辟空间存放 指向对中的地址,栈中名称为 arr1 var arr2 = arr1; // 在栈中开辟空间存放 指向对中的地址,栈中名称为 arr2 这里 arr2存放的堆地址 与 arr1的堆地址相同。 arr1.push(5); console.log(arr2); |
那么如果arr1重新赋值,arr2 会随之 arr1的赋值而改变吗?
其实是不会的,原因是,在堆中,arr1的存储过程中,需要重新再开辟一个空间来存放 arr1最新的一次赋值,当然arr1在栈中指向堆的引用地址就发生了改变,这个时候,arr2在栈中的引用地址 和 arr1的地址不同。所以,这个时候,arr2 不会随着 arr1的赋值而改变。
再看一个Java中的例子:
这个例子输出结果是null;
如果是基本数据类型
1 @Test 2 void test() { 3 int a = 0; 4 int b = a; 5 a = 2; 6 System.out.println(b); 7 8 }
输出结果是0;因为b在栈空间中存的是0,不是地址值;
再看一个对象的内存解析:
Java知识点补充:
一、栈(Stack ),是指虚拟机栈。虚拟机栈用于存储局部变量等。局部变量表存放了编译期可知长度的各种基本数据类型(boolean、byte、char、short、int、float、 long、double)、对象引用 (reference类型,它不等同于对象本身,是对象在堆内存的首地址)。方法执行完,自动释放。
- 栈:先进后出
- 方法中定义的变量都是局部变量,存在于栈空间中
二、堆(Heap),此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。这一点在 Java虚拟机规范中的描述是:所有的对象实例以及数组都要在堆上分配。
- 将new出来的结构(比如:数组、对象)加载在对空间中。
- 补充:对象的属性(非static的)加载在堆空间中
三、方法区(Method Area),用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
注意点:编译完源程序以后,生成一个或多个字节码文件。
我们使用JVM中的类的加载器和解释器对生成的字节码文件进行解释运行。意味着,需要将字节码文件对应的类加载到内存中,涉及到内存解析。
参考地址:https://www.cnblogs.com/zhufeng123/p/12263011.html