在JavaScript中,若是要複製一個變量咱們應該怎麼作呢?下面這種作法是最簡單的一種形式:javascript
//把a複製給b let a = 12; let b = a;
這種複製方法只能適用於基本類型,若是a
是對象怎麼辦呢?咱們先來看看上面的代碼在內存中作了什麼事: java
聲明瞭變量a = 12
,棧內存會分配一塊區域來存儲,如上圖所示。把a
賦給b
,會在棧中從新開闢一塊區域來存儲b
,而且b
的值就是a
的值。數組
假如a
是對象,內存作了什麼事呢?來看下面的例子:函數
let a = {}; let b = a;
如圖所示,對象是存儲在堆內存中的,棧中保存的是地址值,使用這種方式複製對象只不過是複製了該對象的引用而已,對象實體仍是隻有一個。那麼對象應該怎樣複製呢?對象的複製其實也就是產生一個如出一轍的對象,對象包含屬性和方法,咱們建立一個新對象,將舊對象的屬性和方法賦給新對象,這樣不就是複製了一個對象嗎?順着這個思路,咱們來看下面的代碼:.net
function copy(obj){ //基本類型和函數直接返回 if(!(obj instanceof Object) || typeof obj === 'function') return obj; let newObj = {}; if(obj instanceof Array) newObj = []; for(let p in obj){ newObj[p] = obj[p]; } return newObj; } let p = { name: 'bob', friends: ['jack', 'rose'] } let p2 = copy(p); console.log(p2);
定義一個copy
函數,接收一個參數,用以實現對象的複製,若是參數是基本類型或函數就直接返回。函數體內聲明一個新對象newObj
,而後遍歷參數obj
,將其每個屬性都賦給newObj
,最後返回newObj
。接着使用copy
方法生成了p
的一個複製對象p2
,結果以下圖所示:3d
可是這樣有一個問題,咱們來看下面的代碼:code
p2.friends.push('lily'); console.log(p2.friends);//["jack", "rose", "lily"] console.log(p.friends);//["jack", "rose", "lily"]
咱們給p2
的friends
添加了一個lily
,結果導致p
的friends
也被同步修改了。下面的內存圖能夠幫助讀者理解:對象
從圖中能夠看出,雖然p
和p2
分別指向了兩個對象,可是裏面的friends
屬性仍是指向的同一個數組,問題在於這段代碼:blog
for(let p in obj){ newObj[p] = obj[p]; }
只進行了對象第一層的複製,對於對象裏面引用類型的屬性,則進行了地址值的複製,這就是所謂的淺複製,也就是說p.friends
和p2.friends
是同一個對象:繼承
console.log(p.friends == p2.friends);//true
那如何進行完全的複製——深複製呢?思路也很簡單,在遍歷賦值對象屬性的時候,遇到屬性是引用類型的,也須要把這個屬性展開賦值一下,因而咱們能夠用遞歸的思想來實現,以下代碼所示:
//深複製 function deepCopy(obj){ //基本類型和函數直接返回 if(!(obj instanceof Object) || typeof obj === 'function') return obj; let newObj = {}; if(obj instanceof Array) newObj = []; for(let p in obj){ //繼續複製對象裏面的對象 newObj[p] = deepCopy(obj[p]); } return newObj; } let p = { name: 'bob', friends: ['jack', 'rose'] } let p2 = deepCopy(p); p2.friends.push('lily'); console.log(p2.friends);//["jack", "rose", "lily"] console.log(p.friends);//["jack", "rose"] console.log(p.friends == p2.friends);//false
聲明瞭deepCopy
函數,用於實現深複製,函數體和淺複製的函數體基本相同,只是在遍歷要複製的對象的時候添遞歸調用deepCopy
,繼續複製對象裏面的對象。接着演示了deepCopy
的使用,發現修改了p2.friends
並不會影響p.friends
,它們已是兩個對象了。
對於淺複製,ES6中提供了Object.assign()
方法,以下代碼所示:
let p3 = Object.assign({}, p); console.log(p3.friends == p.friends);//true
不知讀者是否還記得,在JavaScript繼承(四)——原型式繼承中提到過Object.create()
方法,從使用效果上來看,Object.create()
建立的新對象有點相似淺複製,只不過新對象和原對象是一種繼承關係,而Object.assign()
建立的新對象和原對象是彼此獨立的,以下代碼所示:
let p4 = Object.create(p); console.log(p4.__proto__ === p);//true console.log(p3.__proto__ === p);//false
這點是讀者須要注意的。