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()會將其變爲空對象,屬性中含有NaNInfinity-Infinity,則序列化的結果會變成null,如果屬性中有函數,undefined,symbol則經過JSON.stringify()序列化後的JSON字符串中這個鍵值對會消失,因爲不支持。

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章