JavaScript淺析 -- 可變對象和不可變對象

一驶鹉、可變對象

把對象a賦值給對象b绩蜻,更改對象b的屬性值,被引用的對象a也隨之改變室埋,這就是可變對象办绝。

var a = {name: '張三', age: 18},
    b = a;
b.name = '李四';
a.name; // "李四"

為啥會這樣呢?其他字符串和數(shù)值怎么不會變姚淆?要搞懂這個問題孕蝉,我們必須了解下其內(nèi)部的存儲邏輯。

JavaScript有7種基本數(shù)據(jù)類型:number腌逢、string降淮、boolean、null搏讶、undefined佳鳖、object 以及 ES6 新增的symbol除了 object 為引用類型媒惕,其他都是基本類型腋颠。基本類型將值存在棧中吓笙,而 object 存在棧中的是地址值而不是本身的內(nèi)容值淑玫,該地址值指向堆里的一塊區(qū)域,該區(qū)域存的才是本身的內(nèi)容值面睛。

var value = 1,
    obj = {name: '張三', age: 18},
    value2 = value,
    obj2 = obj;
變量實(shí)際存儲內(nèi)存圖

可以看到對于基本類型 number絮蒿,其存值是棧里單獨(dú)存一塊,即使賦值也是copy一份再單獨(dú)賦值叁鉴,所以兩者改變互不影響土涝。但對于引用類型的 object,雖然也是在棧里單獨(dú)存一塊并copy值賦值幌墓,但是其存的是地址值但壮,由于copy的地址值相同冀泻,所以最后兩個obj指向的內(nèi)容區(qū)域也相同,所以修改會同步蜡饵。當(dāng)修改 obj.name 改的是堆內(nèi)存里面的 name弹渔,改完后, obj2.name 拿著同樣的地址值去找堆內(nèi)存對應(yīng)區(qū)域溯祸,此時 obj2 看到的是已經(jīng)改后的 name肢专,于是返回改后的 name

var obj = {name: '張三', age: 18},
    obj2 = {name: '張三', age: 18};
obj2.name = '李四';
obj.name; // “張三"

上面這個例子焦辅,改了 obj2.name博杖,obj.name 卻不受影響,這又是為啥呢筷登?我們來看看內(nèi)存圖剃根。

變量存儲內(nèi)存圖

從內(nèi)存圖可以看到,當(dāng)創(chuàng)建一個新的引用類型變量時前方,會生成一個新的地址值狈醉,由于objobj2 地址不同指向的區(qū)域不同,所以修改之后互不影響镣丑。

array舔糖、function 等也都屬于 object,也都是引用類型莺匠,所以修改數(shù)組的時候金吗,原理和上面的一樣。所以為了使兩個引用類型互不影響趣竣,一般我們不直接將舊的 obj 賦值給新的 obj摇庙,而是生成新的引用類型變量,然后將原先對象的屬性值一個個復(fù)制填充到新的引用類型變量里遥缕,這就是所謂的深拷貝卫袒。

二、不可變對象

竟然對象可能會被其他擁有同樣引用地址的對象同步改變单匣,那么除了上述說的深拷貝一個新對象之外夕凝,有沒有辦法讓原對象也不可改寫?有的户秤,JavaScript 確實(shí)提供了一些原生的方法來將可變對象變成不可變對象码秉,通過控制對象屬性的增刪改來實(shí)現(xiàn),由弱到強(qiáng)依次分為三種:Object.preventExtensions(不可擴(kuò)展)鸡号、Object.seal(密封)转砖、Object.freeze(凍結(jié))。

1. 不可擴(kuò)展

Object.preventExtensions() 可以使一個對象不可再添加新的屬性鲸伴,參數(shù)為目標(biāo)對象府蔗,返回修改后的對象晋控。

var obj = Object.preventExtensions({});

// 直接定義新的屬性會報錯
Object.defineProperty(obj, 'content', {
  value: 'hello'
}); // TypeError: Cannot define property:p, object is not extensible.

// 非嚴(yán)格模式下通過點(diǎn)符號添加不會報錯,但會靜默失敗姓赤,原對象仍然沒有 content 屬性
obj.content = 'hello';
obj.content; // undefined

對應(yīng)的赡译,Object.isExtensible() 可以判斷一個對象是否可擴(kuò)展,即是否可以添加新的屬性模捂。參數(shù)是目標(biāo)對象捶朵,返回布爾值蜘矢, true 代表可擴(kuò)展狂男,false 不可擴(kuò)展。

var obj = new Object();
Object.isExtensible(obj); // true
Object.preventExtensions(obj);
Object.isExtensible(obj); // false
2. 密封

Object.seal() 可以使一個對象無法添加新屬性的同時品腹,也無法刪除舊屬性岖食。參數(shù)是目標(biāo)對象,返回修改后的對象舞吭。

其本質(zhì)是通過修改屬性的 configurablefalse 來實(shí)現(xiàn)的泡垃。在屬性描述對象里講到,configurablefalse 時羡鸥,其他配置不可改變蔑穴,writable 只能 truefalse,且屬性無法被刪除惧浴。而由于只要 writableconfigurable 其中之一為 true存和,則 value 可改,所以密封之后的對象還是可以改屬性值的衷旅。

var obj = {content: 'hello'};
Object.getOwnPropertyDescriptor(obj, 'content');
// Object {
//   value: "hello",
//   writable: true,
//   enumerable: true,
//   configurable: true
// }
Object.seal(obj);
Object.getOwnPropertyDescriptor(obj, 'content'); // seal 后 configurable 變?yōu)?false
// Object {
//   value: "hello",
//   writable: true,
//   enumerable: true,
//   configurable: false
// }

對應(yīng)的捐腿,Object.isSealed() 可以檢測一個對象是否密封,即是否可以增刪屬性柿顶。參數(shù)是目標(biāo)對象茄袖,返回布爾值,true 代表被密封不可增刪屬性嘁锯,false 代表沒被密封可增刪屬性宪祥。

var obj = new Object();
Object.isExtensible(obj); // true
Object.isSealed(obj); // false
Object.seal(obj);
Object.isExtensible(obj); // false,注意 seal 后對象的 isExtensible() 也隨之改變
Object.isSealed(obj); // true
3. 凍結(jié)

Object.freeze() 可以使對象一個對象不能再添加新屬性家乘,也不可以刪除舊屬性蝗羊,且不能修改屬性的值。參數(shù)是目標(biāo)對象烤低,返回修改后的對象肘交。

var obj = Object.freeze({name: 'example'});

// 直接定義新的屬性會報錯
Object.defineProperty(obj, 'content', {
  value: 'hello'
}); // TypeError: Cannot define property:p, object is not extensible.

// 非嚴(yán)格模式下通過點(diǎn)符號添加不會報錯,但會靜默失敗扑馁,原對象仍然沒有 content 屬性
obj.content = 'hello';
obj.content; // undefined

delete obj.name; // 刪除失敗涯呻,返回 false

obj.name = 'hello';
obj.name; // 仍然是 "example"

對應(yīng)的凉驻,Object.isFrozen() 可以檢測一個對象是否凍結(jié),即是否可以增刪改复罐。參數(shù)是目標(biāo)對象涝登,返回布爾值,true 表示已經(jīng)凍結(jié)不可再增刪改效诅,false 反之胀滚。

var obj = new Object();
Object.isExtensible(obj); // true
Object.isSealed(obj); // false
Object.isFrozen(obj); // false
Object.freeze(obj);
Object.isExtensible(obj); // false,注意 freeze 后對象的 isExtensible() 也隨之改變
Object.isSealed(obj); // true乱投,注意 freeze 后對象的 isSealed() 也隨之改變
Object.isFrozen(obj); // true
4. 注意要點(diǎn)

無論是不可擴(kuò)展咽笼,密封,還是凍結(jié)戚炫,都是淺層控制的剑刑,即只控制對象本身屬性的增刪改。如果對象屬性是一個引用類型双肤,比如數(shù)組 subArr 或?qū)ο?subObj等施掏,雖然subArr、subObj 的不可被刪改茅糜,但subArr七芭、subObj屬性仍然可增刪改。

var obj = Object.freeze({
  content: {name: 'example'}
});
obj.content = new Object();
obj.content; // {name: "example"}蔑赘,content 本身不可改
obj.content.name = 'test';
obj.content; // {name: "test"}狸驳,但 content 的屬性仍可改,因?yàn)閮鼋Y(jié)的是 obj 而不是 obj.content

由于每個對象都有一個屬性 __proto__米死,該屬性的值是該對象的原型對象锌历,也是引用類型,由于凍結(jié)是淺層的所以原型對象并不會被連著凍結(jié)峦筒,仍然可以通過給對象的原型對象加屬性達(dá)到給當(dāng)前對象新增屬性的效果究西。所以如果想進(jìn)一步凍結(jié)還需要把原型對象也凍結(jié)上。

var obj = Object.freeze({});
obj.content = 'hello';
obj.content; // undefined物喷,增加失敗

var proto = Object.getPrototypeOf(obj);
proto.content = 'hello';
obj.content; // "hello"卤材,增加成功

Object.freeze(proto);
proto.name = 'example';
obj.name; // undefined,凍結(jié)原型之后增加失敗
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末峦失,一起剝皮案震驚了整個濱河市扇丛,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌尉辑,老刑警劉巖帆精,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡卓练,警方通過查閱死者的電腦和手機(jī)隘蝎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來襟企,“玉大人嘱么,你說我怎么就攤上這事⊥绲浚” “怎么了曼振?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長蔚龙。 經(jīng)常有香客問我冰评,道長,這世上最難降的妖魔是什么府蛇? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任集索,我火速辦了婚禮屿愚,結(jié)果婚禮上汇跨,老公的妹妹穿的比我還像新娘。我一直安慰自己妆距,他們只是感情好穷遂,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著娱据,像睡著了一般蚪黑。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上中剩,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天忌穿,我揣著相機(jī)與錄音,去河邊找鬼结啼。 笑死掠剑,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的郊愧。 我是一名探鬼主播朴译,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼属铁!你這毒婦竟也來了眠寿?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤焦蘑,失蹤者是張志新(化名)和其女友劉穎盯拱,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡狡逢,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年迹辐,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片甚侣。...
    茶點(diǎn)故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡明吩,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出殷费,到底是詐尸還是另有隱情印荔,我是刑警寧澤,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布详羡,位于F島的核電站仍律,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏实柠。R本人自食惡果不足惜水泉,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望窒盐。 院中可真熱鬧草则,春花似錦、人聲如沸蟹漓。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽葡粒。三九已至份殿,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間嗽交,已是汗流浹背卿嘲。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留夫壁,地道東北人拾枣。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像掌唾,于是被迫代替她去往敵國和親放前。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,976評論 2 355

推薦閱讀更多精彩內(nèi)容