W3Cschool
恭喜您成為首批注冊用戶
獲得88經(jīng)驗值獎勵
對象與原始類型的根本區(qū)別之一是,對象是“通過引用”存儲和復制的,而原始類型:字符串、數(shù)字、布爾值等 —— 總是“作為一個整體”復制。
如果我們深入了解復制值時會發(fā)生什么,就很容易理解了。
讓我們從原始類型開始,例如一個字符串。
這里我們將 message
復制到 phrase
:
let message = "Hello!";
let phrase = message;
結果我們就有了兩個獨立的變量,每個都存儲著字符串 "Hello!"
。
顯而易見的結果,對吧?
但是,對象不是這樣的。
賦值了對象的變量存儲的不是對象本身,而是該對象“在內(nèi)存中的地址” —— 換句話說就是對該對象的“引用”。
讓我們看一個這樣的變量的例子:
let user = {
name: "John"
};
這是它實際存儲在內(nèi)存中的方式:
該對象被存儲在內(nèi)存中的某個位置(在圖片的右側),而變量 user
(在左側)保存的是對其的“引用”。
我們可以將一個對象變量(例如 user
)想象成一張寫有對象的地址的紙。
當我們對對象執(zhí)行操作時,例如獲取一個屬性 user.name
,JavaScript 引擎會查看該地址中的內(nèi)容,并在實際對象上執(zhí)行操作。
現(xiàn)在,這就是為什么它很重要。
當一個對象變量被復制 —— 引用被復制,而該對象自身并沒有被復制。
例如:
let user = { name: "John" };
let admin = user; // 復制引用
現(xiàn)在我們有了兩個變量,它們保存的都是對同一個對象的引用:
正如你所看到的,這里仍然只有一個對象,但現(xiàn)在有兩個引用它的變量。
我們可以通過其中任意一個變量來訪問該對象并修改它的內(nèi)容:
let user = { name: 'John' };
let admin = user;
admin.name = 'Pete'; // 通過 "admin" 引用來修改
alert(user.name); // 'Pete',修改能通過 "user" 引用看到
這就像我們有一個帶有兩把鑰匙的柜子,使用其中一把鑰匙(admin
)打開柜子并更改了里面的東西。那么,如果我們稍后用另一把鑰匙(user
),我們?nèi)匀豢梢源蜷_同一個柜子并且可以訪問更改的內(nèi)容。
僅當兩個對象為同一對象時,兩者才相等。
例如,這里 a
和 b
兩個變量都引用同一個對象,所以它們相等:
let a = {};
let b = a; // 復制引用
alert( a == b ); // true,都引用同一對象
alert( a === b ); // true
而這里兩個獨立的對象則并不相等,即使它們看起來很像(都為空):
let a = {};
let b = {}; // 兩個獨立的對象
alert( a == b ); // false
對于類似 obj1 > obj2
的比較,或者跟一個原始類型值的比較 obj == 5
,對象都會被轉換為原始值。我們很快就會學到對象是如何轉換的,但是說實話,很少需要進行這樣的比較 —— 通常是在編程錯誤的時候才會出現(xiàn)這種情況。
那么,拷貝一個對象變量會又創(chuàng)建一個對相同對象的引用。
但是,如果我們想要復制一個對象,那該怎么做呢?
我們可以創(chuàng)建一個新對象,通過遍歷已有對象的屬性,并在原始類型值的層面復制它們,以實現(xiàn)對已有對象結構的復制。
就像這樣:
let user = {
name: "John",
age: 30
};
let clone = {}; // 新的空對象
// 將 user 中所有的屬性拷貝到其中
for (let key in user) {
clone[key] = user[key];
}
// 現(xiàn)在 clone 是帶有相同內(nèi)容的完全獨立的對象
clone.name = "Pete"; // 改變了其中的數(shù)據(jù)
alert( user.name ); // 原來的對象中的 name 屬性依然是 John
我們也可以使用 Object.assign 方法來達成同樣的效果。
語法是:
Object.assign(dest, [src1, src2, src3...])
dest
?是指目標對象。src1, ..., srcN
?(可按需傳遞多個參數(shù))是源對象。dest
?中。換句話說,從第二個開始的所有參數(shù)的屬性都被拷貝到第一個參數(shù)的對象中。dest
?。例如,我們可以用它來合并多個對象:
let user = { name: "John" };
let permissions1 = { canView: true };
let permissions2 = { canEdit: true };
// 將 permissions1 和 permissions2 中的所有屬性都拷貝到 user 中
Object.assign(user, permissions1, permissions2);
// 現(xiàn)在 user = { name: "John", canView: true, canEdit: true }
如果被拷貝的屬性的屬性名已經(jīng)存在,那么它會被覆蓋:
let user = { name: "John" };
Object.assign(user, { name: "Pete" });
alert(user.name); // 現(xiàn)在 user = { name: "Pete" }
我們也可以用 Object.assign
代替 for..in
循環(huán)來進行簡單克?。?
let user = {
name: "John",
age: 30
};
let clone = Object.assign({}, user);
它將 user
中的所有屬性拷貝到了一個空對象中,并返回這個新的對象。
還有其他克隆對象的方法,例如使用 spread 語法 clone = {...user}
,在后面的章節(jié)中我們會講到。
到現(xiàn)在為止,我們都假設 user
的所有屬性均為原始類型。但屬性可以是對其他對象的引用。
例如:
let user = {
name: "John",
sizes: {
height: 182,
width: 50
}
};
alert( user.sizes.height ); // 182
現(xiàn)在這樣拷貝 clone.sizes = user.sizes
已經(jīng)不足夠了,因為 user.sizes
是個對象,它會以引用形式被拷貝。因此 clone
和 user
會共用一個 sizes:
let user = {
name: "John",
sizes: {
height: 182,
width: 50
}
};
let clone = Object.assign({}, user);
alert( user.sizes === clone.sizes ); // true,同一個對象
// user 和 clone 分享同一個 sizes
user.sizes.width++; // 通過其中一個改變屬性值
alert(clone.sizes.width); // 51,能從另外一個獲取到變更后的結果
為了解決這個問題,并讓 user
和 clone
成為兩個真正獨立的對象,我們應該使用一個拷貝循環(huán)來檢查 user[key]
的每個值,如果它是一個對象,那么也復制它的結構。這就是所謂的“深拷貝”。
我們可以使用遞歸來實現(xiàn)它。或者為了不重復造輪子,采用現(xiàn)有的實現(xiàn),例如 lodash 庫的 _.cloneDeep(obj)。
使用 const 聲明的對象也是可以被修改的
通過引用對對象進行存儲的一個重要的副作用是聲明為
const
的對象 可以 被修改。
例如:
const user = { name: "John" }; user.name = "Pete"; // (*) alert(user.name); // Pete
看起來
(*)
行的代碼會觸發(fā)一個錯誤,但實際并沒有。user
的值是一個常量,它必須始終引用同一個對象,但該對象的屬性可以被自由修改。
換句話說,只有當我們嘗試將
user=...
作為一個整體進行賦值時,const user
才會報錯。
也就是說,如果我們真的需要創(chuàng)建常量對象屬性,也是可以的,但使用的是完全不同的方法。我們將在 屬性標志和屬性描述符 一章中學習它。
對象通過引用被賦值和拷貝。換句話說,一個變量存儲的不是“對象的值”,而是一個對值的“引用”(內(nèi)存地址)。因此,拷貝此類變量或?qū)⑵渥鳛楹瘮?shù)參數(shù)傳遞時,所拷貝的是引用,而不是對象本身。
所有通過被拷貝的引用的操作(如添加、刪除屬性)都作用在同一個對象上。
為了創(chuàng)建“真正的拷貝”(一個克?。?,我們可以使用 Object.assign
來做所謂的“淺拷貝”(嵌套對象被通過引用進行拷貝)或者使用“深拷貝”函數(shù),例如 _.cloneDeep(obj)。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號-3|閩公網(wǎng)安備35020302033924號
違法和不良信息舉報電話:173-0602-2364|舉報郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號
聯(lián)系方式:
更多建議: