基本数据类型和引用数据类型的存储区别?

文章详细阐述了JavaScript中基本数据类型和引用数据类型在存储位置、变量赋值以及参数传递上的差异。基本类型存储在栈中,赋值时创建值的副本,而引用类型存储在堆中,赋值时复制对象的引用。在参数传递上,所有函数参数都是按值传递,但原始类型和引用类型的表现有所不同,引用类型的参数实际上是共享传递,即传递对象引用的副本。
摘要由CSDN通过智能技术生成

目录

1、存储位置的区别

2、变量赋值时的区别

① 基本数据类型

② 引用数据类型

3、参数传递的不同

4、小结

① 声明变量时不同的内存地址分配

② 不同的类型数据导致赋值变量时的不同

③ 不同类型数据的参数传递 


1、存储位置的区别

基本数据类型和引用数据类型存储在内存中的位置不同:

① 基本数据类型存储在栈中        ② 引用类型的对象存储于堆中

原始数据类型,也就基本数据类型。它的原始值存储在栈stack中的简单数据段。也就是说,它们的值直接存储在变量访问的位置。这是因为,这些原始类型会占据的空间是固定的,所以可以将它们存储在较小的内存区域——栈中,这样存储便于迅速查询变量的值。

基本数据类型的特点:占据空间小、大小固定,属于被频繁使用数据,所以放入栈中存储;

引用类型的值存储在堆heap中的对象,也就是说,存储在变量处的值是一个指针,指向存储对象的内存地址。这是因为:引用值的大小会改变,所以不能把它放在栈中,否则会降低变量查寻的速度。相反,放在变量的栈空间中的值是该对象存储在堆中的地址。地址的大小是固定的,所以把它存储在栈中对变量性能无任何负面影响。

当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。

堆和栈的概念存在于数据结构和操作系统内存中;


在数据结构中,栈中数据的存取方式为先进后出
堆是一个优先队列,是按优先级来进行排序的,优先级可以按照大小来规定

在操作系统中,内存被分为栈区和堆区
栈区内存由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
堆区内存一般由开发着分配释放,若开发者不释放,程序结束时可能由垃圾回收机制回收. 

2、变量赋值时的区别

当我们把变量赋值给一个变量时,解析器首先要确认的就是这个值是基本类型值还是引用类型值。

① 基本数据类型

基本类型数据是存放在栈中

let a = 10;
let b = a; // 赋值操作
b = 20;
console.log(a); // 10值

a的值为一个基本类型,是存储在栈中,将a的值赋给b虽然两个变量的值相等,但是两个变量保存了两个不同的内存地址。

下图演示了基本类型赋值的过程:

② 引用数据类型

引用类型数据存放在堆中,每个堆内存对象都有对应的引用地址指向它,引用地址存放在栈中

由于这种值的大小不固定,因此不能把它们保存到栈内存中。但内存地址大小的固定的,因此可以将内存地址保存在栈内存中。

当查询引用类型的变量时, 先从栈中读取内存地址, 然后再通过地址找到堆中的值。

var obj1 = {}
var obj2 = obj1;
obj2.name = "小草莓";
console.log(obj1.name); // 小草莓

obj1是一个引用类型,在赋值操作过程中,实际是将堆内存对象在栈内存的引用地址复制了一份给了obj2,实际上他们共同指向了同一个堆内存对象,所以更改obj2会对obj1产生影响。

下图演示这个引用类型赋值过程:

3、参数传递的不同

首先我们应该明确一点:ECMAScript中所有函数的参数都是按值来传递的。

但是为什么涉及到原始类型与引用类型的值时仍然有区别呢,还不就是因为内存分配时的差别。 (我对比了一下,这里和复制变量时遵循的机制完全一样的嘛,你可以简单地理解为传递参数的时候,就是把实参复制给形参的过程

① 原始值:只是把变量里的值传递给参数,之后参数和这个变量互不影响。

function addTen(num) { 
 num += 10; 
 return num; 
} 
let count = 20; 
let result = addTen(count); 
console.log(count); // 20,没有变化
console.log(result); // 30 

② 引用值:对象变量它里面的值是这个对象在堆内存中的内存地址

这一点你要时刻铭记在心!因此它传递的值也就是这个内存地址,这也就是为什么函数内部对这个参数的修改会体现在外部的原因了,因为它们都指向同一个对象呀



所以,如果是按引用传递的话,是把第二格中的内容(也就是变量本身)整个传递进去(就不会有第四格的存在了)。但事实是变量把它里面的值传递(复制)给了参数,让这个参数也指向原对象。

因此如果在函数内部给这个参数赋值另一个对象时,这个参数就会更改它的值为新对象的内存地址指向新的对象,但此时原来的变量仍然指向原来的对象,这时候他们是相互独立的;但如果这个参数是改变对象内部的属性的话,这个改变会体现在外部,因为他们共同指向的这个对象被修改了呀!

来看下面这个例子吧:(传说中的call by sharing) 

var obj1 = {
  value:'111'
};
 
var obj2 = {
  value:'222'
};
 
function changeStuff(obj){
  obj.value = '333';
  obj = obj2;
  return obj.value;
}
 
 
var foo = changeStuff(obj1);
 
console.log(foo); // '222' 参数obj指向了新的对象obj2
console.log(obj1.value); //'333'

obj1仍然指向原来的对象, 之所以value改变了, 是因为changeStuff里的第一条语句,这个时候obj是指向obj1的 。再啰嗦一句,如果是按引用传递的话,这个时候obj1.value应该是等于'222'的

例子2:

function setName(obj) { 
 obj.name = "Nicholas"; 
 obj = new Object(); 
 obj.name = "Greg"; 
} 
let person = new Object(); 
setName(person); 
console.log(person.name); // "Nicholas"

当 person 传入 setName()时,其 name 属性被设置为"Nicholas"。然后变量 obj 被设置
为一个新对象且 name 属性被设置为"Greg"。

如果 person 是按引用传递的,那么 person 应该自动将指针改为指向 name 为"Greg"的对象。可是,当我们再次访问 person.name 时,它的值是"Nicholas",这表明函数中参数的值改变之后,原始的引用仍然没变。

当 obj 在函数内部被重写时,它变成了一个指向本地对象的指针。而那个本地对象在函数执行结束时就被销毁了。
注意 ECMAScript 中函数的参数就是局部变量。

例子3:

function test(m) {
  m = { v: 5 };
}
var m = {
  k: 30
};
test(m);
console.log(m); // { k:30 }
var obj = {
    value: 1
};
function foo(o) {
    o = 2;
    console.log(o); // 2
}
foo(obj);
console.log(obj.value) // 1

如果 JavaScript 采用的是引用传递,外层的值也会被修改呐,这怎么又没被改呢?所以真的不是引用传递吗?

这就要讲到其实还有第三种传递方式,叫按共享传递

而共享传递是指,在传递对象的时候,传递对象的引用的副本。

注意:

按引用传递是传递对象的引用,而按共享传递是传递对象的引用的副本!

所以修改 o.value,可以通过引用找到原值,但是直接修改 o,并不会修改原值。所以第二个和第三个例子其实都是按共享传递。

最后,你可以这样理解:

参数如果是基本类型是按值传递,如果是引用类型按共享传递。

但是因为拷贝副本也是一种值的拷贝,所以在高程中也直接认为是按值传递了。

4、小结

① 声明变量时不同的内存地址分配

简单数据类型存储在栈中,在栈中存放的是对应的值,是按值来访问的

引用数据类型对应的值存储在堆中,在栈中存放的是指向堆内存的地址,是按引用访问

在javascript中是不允许直接访问保存在堆内存中的对象的,所以在访问一个对象时,首先得到的是这个对象在堆内存中的地址,然后再按照这个地址去获得这个对象中的值,这就是传说中的 按引用访问

而原始类型的值则是可以直接访问到的。 

② 不同的类型数据导致赋值变量时的不同

简单类型赋值,是生成相同的值,两个变量对应不同的地址,是完全独立的;

复杂类型赋值,是将保存对象的内存地址赋值给另一个变量。也就是两个变量指向堆内存中同一个对象;当其中一个对象改变时,另一个对象也会变化。

简单类型赋值:在将一个保存着原始值的变量复制给另一个变量时,会将原始值的副本赋值给新变量,此后这两个变量是完全独立的,他们只是拥有相同的value而已。

复杂类型赋值:复制对象时并不会在堆内存中新生成一个一模一样的对象,只是多了一个保存指向这个对象指针的变量罢了

③ 不同类型数据的参数传递 

参数如果是基本类型是按值传递,如果是引用类型按共享传递,传递对象的引用的副本!

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值