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

一、基本类型VS引用类型

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

1、引用类型:

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

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

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

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

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

3、实例一:基本类型

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

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

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

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

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

  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、我们先看一下基本类型值的比较:

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

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

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

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

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

如图:


image

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

function  sum(){ 
              console.log('sum...')
     }  
var sum2 = sum; 
 sum2()  
//返回sum... 二者是相等的

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

如图:


image

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

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

二、引用类型的实际应用

1、函数的参数传递

第1个例子:

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个例子:

     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个例子:

      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个例子:

        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、对象的深浅拷贝

针对这个例子:

     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的前半部分或者局部,那么这称之为浅拷贝,如:

      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仍然不会发生变化,而这叫做深拷贝,如:

       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——对象

你可能感兴趣的:(#hello,JS:05引用类型和深浅拷贝)