您好,登錄后才能下訂單哦!
在JavaScript中,如果要復制一個變量我們應該怎么做呢?下面這種做法是最簡單的一種形式:
//把a復制給b
let a = 12;
let b = a;
這種復制方法只能適用于基本類型,如果a是對象怎么辦呢?我們先來看看上面的代碼在內存中做了什么事:
聲明了變量a = 12,棧內存會分配一塊區域來存儲,如上圖所示。把a賦給b,會在棧中重新開辟一塊區域來存儲b,并且b的值就是a的值。
假如a是對象,內存做了什么事呢?來看下面的例子:
let a = {};
let b = a;
如圖所示,對象是存儲在堆內存中的,棧中保存的是地址值,使用這種方式復制對象只不過是復制了該對象的引用而已,對象實體還是只有一個。那么對象應該怎樣復制呢?對象的復制其實也就是產生一個一模一樣的對象,對象包含屬性和方法,我們創建一個新對象,將舊對象的屬性和方法賦給新對象,這樣不就是復制了一個對象嗎?順著這個思路,我們來看下面的代碼:
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,結果如下圖所示:
但是這樣有一個問題,我們來看下面的代碼:
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屬性還是指向的同一個數組,問題在于這段代碼:
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
感興趣的可以自己來我的Java架構群,可以獲取免費的學習資料,群號:855801563 對Java技術,架構技術感興趣的同學,歡迎加群,一起學習,相互討論。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。