1. 程式人生 > >js物件的直接賦值、淺拷貝與深拷貝

js物件的直接賦值、淺拷貝與深拷貝

  最近Vue專案中寫到一個業務,就是需要把對話方塊的表單中的資料,每次點選提交之後,就存進一個el-table表格中,待多次需要的表單資料都提交進表格之後,再將這個表格提交,實現多個表單資料的同時提交,期間還可以用表格進行預覽、修改等其他操作。將每個表單資料存進表格的程式碼大致程式碼如下:

    let object=this.ruleForm;

    this.tableData.push(object);

  其中,對話方塊中的表單使用了el-form,this.ruleForm是vue例項中的一個物件,而this.tableData是vue例項中的一個數組物件。直接將this.ruleForm賦值給一個變數object,然後每次再push進this.tableData裡,這樣看上去邏輯似乎也沒啥毛病,但是,這樣就會產生一個神奇的現象:每次填寫表單中的資料的時候,表格中的每一行資料都會隨著你表單的填寫的改變而改變。

  這裡就是出現了題目所談到的問題,涉及到了js物件的直接賦值、淺拷貝與深拷貝。

直接賦值

  把一個物件a賦值給一個物件b相當於把一個物件b的地址指向物件a的地址,所以,他們實際上是同一個物件。由於這個專案是Vue,這次的問題就出現在了直接賦值上,Vue的響應式會讓你更直觀的知道他們的實質。以圖1直接賦值的例子,person物件中有兩個屬性,一個是name,一個是物件屬性ageAndSex;為什麼要弄一個物件屬性,這個會涉及到後面的淺拷貝和深拷貝問題,這也是他們之間的區別。由於記憶體地址我們很難監測到,但是我們可以通過嚴格相等運算子"==="來檢測二者是否指向同一個地址。

 圖1 如果二者都是物件,嚴格相等運算子則會去檢查它們是否指向相同的記憶體地址。

  以剛才的例子為例,如圖2所示。剛開始的時候給personCopy的name屬性賦值小剛,發現,person也發生了改變。給personCopy的物件屬性ageAndSex的age屬性賦值17,person也發生了改變。即:直接賦值,修改賦值後的物件b的非物件屬性,也會影響原物件a的非物件屬性;修改賦值後的物件b的物件屬性,也會影響原物件a的物件屬性。

圖2 直接賦值

淺拷貝

  淺拷貝只會賦值制物件的非物件屬性,不會指向同一個地址。ES6中有個淺拷貝的方法Object.assign(target, ...sources)。以之前直接賦值的物件為例,如圖3所示。

圖3 淺拷貝,賦值的物件與被複制的物件不會指向同一個地址

  修改賦值後的物件b的非物件屬性,不會影響原物件a的非物件屬性;修改賦值後的物件b的物件屬性,卻會影響原物件a的物件屬性,如圖4所示。

圖4 淺拷貝

  es6中還有一個擴充套件運算子"..."也可以實現淺拷貝,還是以之前的物件為例,可以寫成這種形式:var personCopy= { ...person };如圖5所示。

圖5 擴充套件運算子實現淺拷貝(賦值"小剛"等的操作與之前的結果完全相同,就不全貼出來了)

   考慮到es6的支援程度,如果你的專案不支援es6,但是又想實現淺拷貝的話,也可以嘗試js原生的concat方法。但由於concat只能運算元組,所以需要先將person封裝為一個物件陣列,寫成這種形式:

    var person=[{name:"小明",ageAndSex:{age:16,sex:"男"}}];

    var personCopy=[].concat(person);

如圖6所示,到時想得到person物件的時候var personCopyObjet=pesronCopy[0]即可。

 圖6 concat方法實現淺拷貝

深拷貝

  深拷貝會另外拷貝一份一個一模一樣的物件,但是不同的是會從堆記憶體中開闢一個新的區域存放新物件,新物件跟原物件不再共享記憶體,修改賦值後的物件b不會改到原物件a。即深拷貝,修改賦值後的物件b的非物件屬性,不會影響原物件a的非物件屬性;修改賦值後的物件b的物件屬性,也不會影響原物件a的物件屬性。而且,二者不指向同一個物件。

  很明顯,深拷貝比較符合我這次的業務需求。深拷貝,比較笨一點的辦法就是將自己需要的資料自己封裝起來。

      let object={                            repayment:this.ruleForm.repayment,                            interestType:this.ruleForm.interestType,                            productDeadline:this.ruleForm.productDeadline,                            circumstancesOfDetention:this.ruleForm.circumstancesOfDetention,                            }                       this.tableData.push(object);

  但是,這樣明顯會使程式碼很臃腫,而且,這還是在需要的資料只有4條的情況下,如果這個object需要封裝十幾條非物件屬性的情況下,明顯結構不復雜的情況下,這種程式碼需要改進。

  有一種非常簡單的方法就是序列化成為一個JSON字串,將物件的內容轉換成字串的形式,再用JSON.parse()反序列化將JSON字串變成一個新的物件,這樣原物件就與複製後的新物件沒了必然的關係。以前文提到的personCopy和person為例,寫法如下:var personCopy=JSON.parse(JSON.stringify(person));如圖7所示。

圖7 深拷貝

  但是由於用到了JSON.stringify(),這也會導致一系列的問題,因為要嚴格遵守JSON序列化規則:原物件中如果含有Date物件,JSON.stringify()會將其變為字串,之後並不會將其還原為日期物件。或是含有RegExp物件,JSON.stringify()會將其變為空物件,屬性中含有NaN、Infinity和-Infinity,則序列化的結果會變成null,如果屬性中有函式,undefined,symbol則經過JSON.stringify()序列化後的JSON字串中這個鍵值對會消失,因為不支援。

  所以,這個時候笨的辦法也是有好處的,就是面對一些特殊的型別,或是物件屬性複雜的情況下,因為自己對程式的需求比較瞭解,就可以按照自己的需要進行封裝。不管黑貓白貓,能抓到老鼠的就是好