1. 程式人生 > >JavaScript中淺拷貝和深拷貝,棧堆詳解

JavaScript中淺拷貝和深拷貝,棧堆詳解

版權宣告:本文為博主原創文章,未經博主允許不得轉載。 https://blog.csdn.net/weixin_40983119/article/details/82995151

簡單來說,JavaScript中單個等於號的賦值就是拷貝,但是呢這個這個賦值可以分為兩種,一種是賦的是資料,一種是賦記憶體地址,在看深淺拷貝前,先來看下JavaScript的資料型別,為甚麼看JavaScript的資料型別呢

因為深淺拷貝與資料型別有很大很大的關係,這能幫助你更好的理解深淺拷貝這個知識點。

ECMAStript變數包含兩種不同型別的值,基本型別和引用型別。

  • 基本型別:指的就是儲存在棧記憶體中的簡單資料段。
  • 引用型別:指的是那些儲存在堆記憶體中的物件,換句話說,就是變數名實際上是一個指標,而這個指標指向的位置,就是儲存物件的位置。

兩種資料型別對應兩種不同的訪問方式

  • 基本型別:按值訪問,操作的是它們實際的值,基本的資料型別有:undefined,boolean,number,string,null.
  • 引用型別:按引用訪問,當查詢時,我們需要先從棧中讀取記憶體地址,然後按照指標所指向的地方,找到堆記憶體裡面的值。

引用型別:javascript中除了上面的基本型別(number,string,boolean,null,undefined)之外就是引用型別了,也可以說是就是物件了。主要有:

  • Object是一個基礎型別,其他所有型別都是從Object繼承基本的行為;
  • Array型別是一組值的有序列表,同事還提供了操作和轉換這些值的功能;
  • Date型別提供有關日期和時間資訊,包括當前日期和時間已經相關的計算功能;
  • RegExp型別是支援正則表示式的。
  • function,函式實際上是Function型別的例項,因此函式也是物件,函式也擁有方法,可以來增強其行為。

耐心看下去哦,這並沒有跑偏話題哦!

說到這裡很有必要說下棧和堆記憶體了,什麼是棧和堆?在計算機領域中,堆疊是兩種資料結構,它們只能在一端(稱為棧頂(top))對資料項進行插入和刪除;可以理解為兩種資料結構。

  • 棧:先進後出;動態分配的空間 一般由程式設計師分配釋放, 若程式設計師不釋放,程式結束時可能由OS回收,分配方式倒是類似於連結串列。
  • 堆:佇列優先,先進先出;由操作系統自動分配釋放 ,存放函式的引數值,區域性變數的值等。其操作方式類似於資料結構中的棧。

JavaScript中的基本型別和引用型別與堆疊有什麼聯絡?

  •  基本型別:Undefined、Null、Boolean、Number 和 String,這5中基本資料型別,是按照值進行分配的,存放在棧(stack)記憶體中的簡單資料段,資料大小確定,記憶體空間大小可以分配。 值得注意的是存在棧記憶體中的資料,一般都是大小已經確定的資料(不同於陣列或物件這兩者,比如陣列的長度一般是可以動態變化動態增刪改的,物件也同理);
  • 引用型別:即存放在堆(heap)記憶體中的物件,變數實際儲存的是一個指標,這個指標指向另一個位置。

回到正題:

  • 深拷貝:其實就是基本資料型別在棧記憶體中的賦值,這個賦值是什麼意思呢?在棧記憶體中開闢一個記憶體空間,裡面放著著你的基本資料型別,具體點就是增加一個指標(指標你可以理解為記憶體索引)並且申請一個新的記憶體,使這個增加的指標(記憶體索引)指向這個新的記憶體,然後你就可以根據指標(記憶體索引)找到你要的資料了(這也是獲取棧記憶體中資料的原理)。
  • 淺拷貝:其實就是引用資料型別在堆中的引用(什麼叫引用,前面說了就是記憶體地址),說白了就是把這個記憶體地址複製一份給到另外一個變數。
  • 綜上所得深拷貝是相對基本資料型別來說的,淺拷貝相對引用資料型別來說的,深拷貝拷的是資料,淺拷貝拷的是指標;

好了理論有了,開始上程式碼: 

var obj = {
    name:'張三',
    age:20
};//此時obj是屬於引用型別Object,在堆中開闢一個記憶體地址,裡面放著name和age兩
//個數據,obj為指向堆中這些資料的指標(也就是索引)


//這是淺拷貝,這行程式碼執行的意思是把obj這個指標賦值給b,那麼b也是指標了,也指向堆中的name和age這個資料
var b = obj;

//深拷貝,這行程式碼就是把age這個值賦給c,為啥說是賦值呢,因為age為基本型別他的值是確定的,所以這個
//意思在棧中開闢一個記憶體,裡面放著20這個資料,而c為指向這個20的指標(c理解為記憶體地址或者索引都可以)
var c = obj.age;

console.log(b.name); //張三
console.log(c);      //20
//改變b的name屬性的值,那也等於改變了obj的name屬性的值,為啥這樣呢?因為前面說過了b和obj都是指標
//他們指向的資料name和age都是一樣的,所以通過b.name來改變name的值和通過obj.name來改變name的值
//其實都是一樣的,所以改了b.name也等於改了obj.name
b.name = 'akuan';
c = 22;//基本資料型別相互獨立,所以改了c不會影響到b和obj裡面的age
console.log(obj1.name);     //akuan
console.log(obj1.age);       //22

那麼怎麼拷貝引用型別,又使得他們之間相互獨立不受影響呢?上程式碼

var obj1 = {
        name: "張三",
        age: "30"
};

function deepCopy(source) {
   var result = {};
   for (var key in source) {
       result[key] = typeof source[key] === 'object' ? deepCopy(source[key]) : source[key];
   }
   return result;
}
    //把obj1拷貝給obj2
    var obj2 = deepCopy(obj1);
    
    //這時候改變obj2的name值看下obj1的name會不會變
    //不會變就說明他們之間相互獨立不受影響。
    obj2.name="李四";
    console.log(obj1.name);//張三

 寫到這裡,深淺拷貝和資料型別基本寫完了,記得動手動腦哦,這會更加有利於你理解的