基础值与引用值
- 基础值:一些代表基础数据类型的值,也叫原始值,Number,String,Boolean,Null,Undefined,Symbol这些基本数据类型都是原始值。
var a=10;
var b=20;
b=a;//b=10
a=20;
console.log(a);//20
console.log(b);//10
在栈内存中,内部的值就像帽子一样,赋值也只是将自己的帽子扣到别人头上,自己还在那里站着,可以接受别人给自己扣帽子。
将a赋值给b结束后,即使a再变化(被扣帽子)也跟b没有关系了。
- 引用值:指一些复合类型数据的值,包括Object,function,Array,RegExp,Data,引用值把引用变量存储在栈中,而实际的对象存储在堆中。每一个引用变量都有一根指针指向其堆中的实际对象。
var a = [1,2,3] ;
var b = a ;
a.push(4) ;
console.log(b) ; //输出1,2,3,4
a = [12] ;
console.log(b) ; //输出1,2,3,4
console.log(a) ; //输出12


对于引用值来说,a和b的变量名被存入栈中,赋值更像是把a值位置赋给b的指针,所以两个变量都指向a值的地址。因此,即使数组a赋值数组b结束后,再向a中压入4也会影响b的值。

栈内存与堆内存
内容
JS语言的一个特殊之处在于:不能直接访问内存的位置,不能直接操作对象的内存,操作的是对象的引用(可视为指针,是一个具体的内存的地址)
栈内存用于存储基本型的变量值,堆内存用于存储引用型的值。
例如下面这些声明中,
var str = `我是字符串`,
num = 1,
bl = true,
nu = null,
un = undefined,
obj = {
name: 'reslicma'
}
str,num,bl,nu和nu属于基础类型,栈内存就足够了,存储的就是值本身。而obj作为一个引用值(Array),在栈中仅保存一个指针(或地址)

理解
栈内存就像一个线性的、规则的、大小基本固定的、有序的排列起来的一块块内存空间,每个单元大小固定,规则有序的排列下来,就是栈。所以,在定义一个基本型变量的时候,发生的事情如下:向栈内存申请一块空间,然后把你声明的变量名和这个变量的具体的值本身压入这个申请好的小空间内。
堆内存就像一个不规则的、大小不固定的、无序的一块块内存空间,像上图中我画的堆内存图中,大小不固定,并且每一块堆内存都有一个自己的地址(指针),用来操作它们。在定义一个引用型变量的时候,向堆内存申请一块空间,用于存储引用型的值(对象),同时JS会随机分配给这块堆内存的小空间一个地址。然后,把变量名和地址压入申请好的栈空间。
优缺点
由于基本型变量大小固定,并且操作容易简单,所以把它们放入栈中存储。引用型变量大小不固定,所以把它们分配给堆中,让他们申请空间的时候自己确定大小,这样把它们分开存储能够使得程序运行起来占用的内存最小。
栈内存由于它的特点,所以它的系统效率较高,堆内存需要分配空间和地址,还要把地址存到栈中,所以效率低于栈。
基础值与引用值的操作
复制
- 基础值:
var a=10;
var b=a;
基础类型的值都存放在栈内存中,所以“赋值”操作就是真的赋值,在栈中开辟一个新空间,并将复制的值赋给新空间内的新变量,互不干扰。
- 引用值:
var obj1 = new Object();
var obj2 = obj1;
obj2.name = "我有名字了";
console.log(obj1.name); // 我有名字了

引用值的“赋值”其实是堆内存对象在栈内存的引用地址复制了一份给新变量,由于两个变量对应的地址指向同一个地方,所以如果进行修改会影响两个值。
传递
- 基础值:
let a=10;
function add(x){
x=x+x;
}
add(a);
console.log(a);//10
传递和赋值一样,只是把副本传入函数中,a的值不变,仍为10。
- 引用值:
let obj={
name:"kiki"
};
function newname(a){
a.name="chuchu";//改名函数
}
console.log(obj.name);//kiki
newname(obj);
console.log(obj.name);//chuchu
传递的是地址,因此函数中获取地址后便在获取的地址上操作,导致变量的值发生改变。
let obj={
name:"kiki"
};
function a(a){
a.name = 'xiuxiu';
a = new Object(); //a指向一个新建的object对象
a.name = 'chuchu';
console.log(a.name) //chuchu 相当于重新赋值,这时候a在栈内存保存的是另外一个值的副本或者新的地址
}
console.log(obj.name)//kiki
a(obj)
console.log(obj.name) //xiuxiu
在函数中让a的指向改变,所以在a改变指向后,即使修改name值为“chuchu”,也只是修改新建对象的name值,在最后一行输出的时候还是修改指向前a的name值:“xiuxiu”
总的来说,基本类型和引用类型都是进行值传递,不过基本类型传递的是值的副本,而引用类型传递值的地址。
比较
- 基础值:
let a = 'hello'
let b = 'hello'
console.log(a==b)//true
逐个比较字节来判断是否相等,比较的是值本身,能说明他们所包含的字节信息是相同的
- 引用值:
let a = new Object(1);
let b = new Object(3);
let c = a;
console.log(a);//Number {[[PrimitiveValue]]: 1}
console.log(b);//Number {[[PrimitiveValue]]: 3}
console.log(a==b); //false
console.log(a==c) //true
变量a和b分别指向新object对象,比较的是两个引用地址,而不是他们的原值字节是否相等,所以a和b的比较结果为false值。而c获得了a所指的位置,所以当a和c的比较结果为true。
两者区别
| -- | 基础值 | 引用值 |
|---|---|---|
| 存储 | 栈存储,占用内存固定,使用后销毁 | 堆存储,占用内存不固定,使用不一定被销毁,当没有对象引用时会被回收销毁 |
| 赋值方法 | 1. 拷贝值,创建一个新对象 2.保存与赋值的是值本身 3.两个数据在内存中完全独立 |
1.拷贝引用,创建一个新引用 2.保存与复制的是指向对象的一个指针 3.变量中的存储的地址赋值一份单独存储,两个变量中修改其中一个对象,另外一个引用也会访问到修改后的值。 |
| 传递 | 值传递 | 引用(地址)传递 |
| 比较方式 | 1. 值比较 2.只比较值 |
1.引用(地址)比较 2.不仅比较值,还比较数据类型 |