hello,JS:05引用类型和深浅拷贝

一、基本类型VS引用类型

注: 这里的内存,为虚拟内存

1、引用类型:

  • 定义:保存在堆内存中的对象,变量中保存的实际上只是一个指针,这个指针执行内存中的另一个位置,由该位置保存对象
  • 包括:对象、数组、函数、正则

假设变量中有一个函数,函数内东西特别多(或者有一个对象,对象里的数据特别大),这里可选堆的空白处存放函数、对象的数据(随机选择未使用的空白堆,随意变大变小),放在堆中的均为引用类型

2、基本类型(值类型):

  • 定义:指的是保存在栈内存中的简单字段(成块排列,栈,允许放进去拿出来)
  • 包括:数值(number)、布尔值(boolean)、nullundefinedstring(在赋值传递中会以引用类型的方式来处理)

栈里面仍存有变量,只不过存放的不是数据,而是大数据地址,比如这个地址为0x0011,栈内存放的东西,均为可控、较小容量。从一个变量向另一个变量赋值基本类型时,会在该变量上创建一个新值,然后再把该值复制到为新变量分配的位置上。

3、实例一:基本类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
  var a 
var b
var obj
var obj2

a = 1;
b = 2;
var obj = {
name: 'xiaoqin',
sex: 'male',
age: 30,
friend: {
name: 'hello', age: 100
}
}
var newObj = {}
b = a;
console.log(b)
//返回1

如图:
image

(1)基本类型的值被赋值给另一个变量,其实就是分配内存空间
一开始,a的值为 1 ,当使用a 来初始化b时,b值此时为1。但b中的1与a中的是完全独立的,该值只是a中的值的一个副本。说明在栈里变量再次变化,但这个两个变量可以参加任何操作而相互不受影响。

总结:
一个变量赋值给另一个变量时,其实是分配了一块新的内存空间。按照以上操作,基本类型在赋值操作后,事实上就a分配了一块新内存空间给b,两个变量是相互不受影响。

(2)基本类型的比较是值的比较 只有在它们的值相等的时候它们才相等。 当比较的两个值的类型不同的时候==运算符会进行类型转换,但是当两个值的类型相同的时候,即使是==也相当于是===

1
2
3
var a = 1;
var b = true;
console.log(a == b);//true

(3)基本类型的变量其实就是存放在栈区。结合以上,栈区指内存里的栈内存,但是栈区里包括了变量名和变量值。

4、实例二:(续上面的例子)引用类型

(1)引用类型的值是可变的
可为引用类型添加属性和方法,也可以删除其属性和方法。
看一下这个例子:一个为引用类型的变量赋值给另一个为引用类型的变量

1
2
3
4
5
  var obj2 = obj //控制台测试一下二者的值
obj
// {name: "ruoyu", sex: "male", age: 30, friend: {…}}
obj2
// {name: "ruoyu", sex: "male", age: 30, friend: {…}}

值是一样的。因为var obj2=obj,即通过obj的值(一个对象)赋值给obj2,那么obj2的值就是赋值后原本obj对应属性和值。作为一个引用类型,它被放在堆中。所以寻找obj2则在堆里找到,只是换了另一个名字为obj2

如图:
image

总结:
原本在栈中的对象分别指向了同一个堆,那么存放在堆中即为对象的内存地址。引用类型的赋值其实是对象保存在栈区地址指针的赋值,因此两个变量指向同一个对象,任何的操作都会相互影响。

(2)引用类型的比较是引用的比较
A、我们先看一下基本类型值的比较:

1
2
3
4
var obj3 =  '{name: 'hello'}';  
var obj4 = '{name: 'hello'}';
console.log( obj3 == obj4 );
// true

总结:
可以得出基本类型的比较:当两个比较值的类型相同(如字符串)的时候,相当于是用 === ,所以输出是true。

B、再来看一下引用类型值的比较:

1
2
3
4
var obj3 =  {name:  'hello'}
var obj4 = {name: 'hello'}
obj3 === obj4
//返回false,说明二者并不相等

为什么是false?不相等呢?
放在栈中的变量 obj3obj4,声明前置均为undefined,当两者均被被声明值的时候,是两个对象,引用类型是引用访问,相当于在堆中分别开辟了两个空间,堆中会有对应的属性+值,此时这两个对象在堆中存的便是堆的地址。obj4obj3一样都开辟了新的堆空间,但是存放的地址也不一样。判断obj3是否与obj4相等,看了分析之后,便知道堆存放的地址并不同,二者也就不相等

如图:
image

(3)引用类型的值是同时保存在栈内存和堆内存中的对象

1
2
3
4
5
6
function  sum(){ 
console.log('sum...')
}
var sum2 = sum;
sum2()
//返回sum... 二者是相等的

我们可以就此分析,函数function sum(),分别有变量sum和函数对象代码(为引用类型,已放在堆中)。之后sum赋值给sum2,即sum2事实上使用的是赋值后sum所指代堆的内存地址,即后续sumsum2共用了堆里的代码(变量的内存地址就像指针一样,通过JS自身引擎找到这个堆),一堆东西起了两个不同的名字

如图:
image

总结: js不同于其他语言,其不允许直接访问内存中的位置,即不能直接操作对象的内存空间,实际上,是操作对象的引用,所以引用类型的值是按引用访问的。

准确地说,引用类型的存储需内存的栈区(栈区是指内存里的栈内存)和堆区(堆区是指内存里的堆内存)共同完成,栈区内存保存变量标识符和指向堆内存中该对象的指针,然后,栈区内存地址也可以说是该对象在堆内存的地址。

二、引用类型的实际应用

1、函数的参数传递

第1个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function  inc(n){ 
n++;
}
var a = 10;
inc(a)
console.log(a)

//返回10

/*等同于*/
function inc(){
var n = arguments[0]
n++
}
//在函数的一开始将var a = 10赋值进var n = arguments[0], //n=arguments[0]=10,此时与n++为11并没有返回,所以与a并无关系

var a = 10
inc(a)
console.log(a)

//返回10

✨第2个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
     function  incObj(obj){
//var obj = o //0x0001
obj.n++
}
var o = {n: 10} //o = 0x0001 对其做声明,为一个对象
incObj(o)
console.log(o)

//等同于
function incObj(){
var obj =arguments[0]
obj.n++
}
//incObj(o) 相当于function incObj(){var obj =arguments[0];obj.n++},
//可知道obj=arguments[0]=o,相当于设obj为临时变量,而o= 0x0001 var o = {n: 10} incObj(o) console.log(o)

如图:
image

总结:
引用类型的本质,变量所存的是这个对象的内存地址指向堆,当去做赋值时是把这个地址进行一个赋值;当去访问的时候是通过这个地址去访问这个对象

✨第3个例子:

1
2
3
4
5
6
7
8
9
10
function  squireArr( arr ){  
//var arr = 0x0011
for(var i = 0; i < arr.length; i++){
arr[i] = arr[i] * arr[i];
}
}
var arr = [2,1,3,6]
squireArr(arr)
console.log(arr)
//(4) [4, 1, 9, 36]

即把function squireArr(arr){}中的数组squireArr(arr)里的每一项变为原来的平方,即参数arr为数组里的值,用for循环进行操作,外界调用时,只需调用一次squireArr(arr),事实上数组squireArr(arr)操作就是对arr的操作

✨第4个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
        function  squireArr2( arr ){ 
var newArr = [];
for(var i = 0; i < arr.length; i++){
newArr[i] = arr[i] * arr[i];
}
return newArr;
}
var arr2 = squireArr2(arr)
console.log(arr2) //返回(4) [16, 1, 81, 1296]

arr
// (4) [4, 1, 9, 36]
arr2 --> (4) [16, 1, 81, 1296]

2、对象的深浅拷贝

针对这个例子:

1
2
3
4
5
6
7
8
9
10
11
12
 var obj;  
var obj2;
var obj = {
name: 'ruoyu',
sex: 'male',
age: 30,
friend: {
name: 'hello',
age: 100
}
}
var obj2 = obj;

想要创造一个新的b,那么就需要遍历原始a的每一项属性+值,用来获取成为新个体的b所需的东西,并一一对b进行改造,即从一无所有,改造成与a相似的新个体,此为克隆

如果在遍历的时候,b这个新个体只是遍历a的前半部分或者局部,那么这称之为浅拷贝,如:

1
2
3
4
5
6
7
8
9
function  shallowCopy(oldObj)  { 
var newObj = {};
for(var i in oldObj) {
if(oldObj.hasOwnProperty(i)) {
newObj[i] = oldObj[i];
}
}
return newObj;
}

而如果b是遍历原始a的每一项属性和值,但是b又是一个独立个体,与a不相关,当修改b的时候,a仍然不会发生变化,而这叫做深拷贝,如:

1
2
3
4
5
6
7
8
9
10
11
function  deepCopy(oldObj)  { 
var newObj = {};
for(var key in oldObj) {
if(typeof oldObj[key] === 'object') {
newObj[key] = deepCopy(oldObj[key]);
}else{
newObj[key] = oldObj[key];
}
}
return newObj;
}

json——string——对象

-------------本文结束感谢您的阅读-------------