《JavaScript高級(jí)程序設(shè)計(jì) 第四章》通過內(nèi)存空間探究ES中基本類型和引用類型的異同

在開發(fā)過程中我們對(duì)基本類型和引用類型的一些特性有所了解:

基本類型是按值訪問的蹬屹,所以我們可以盡情地賦值修改不用擔(dān)心有副作用意述;

而引用類型是按引用訪問的宣吱,所以這會(huì)導(dǎo)致當(dāng)我們操作一個(gè)復(fù)制自另一個(gè)引用類型的值時(shí)陡鹃,使得另一個(gè)值也發(fā)生變化。

說起來可能有點(diǎn)拗口疚脐,又是復(fù)制又是賦值的,還有什么按值訪問按引用訪問邢疙,看一下demo:


// 聲明兩個(gè)基本類型的變量棍弄,simpleVariable2復(fù)制自simpleVariable1

let simpleVariable1 = 'simpleVariable1';

let simpleVariable2 = simpleVariable1;

// 打印出兩個(gè)變量的值,沒有什么問題

console.log(simpleVariable1);  // simpleVariable1

console.log(simpleVariable2);  // simpleVariable1

// 修改 simpleVariable2 的值疟游,一切如預(yù)期

simpleVariable2 = 'simpleVariable2';

console.log(simpleVariable1);  // simpleVariable1

console.log(simpleVariable2);  // simpleVariable2

// 聲明兩個(gè)復(fù)雜類型的變量呼畸,complexVariable2復(fù)制自complexVariable1

let complexVariable1 = {

name: 'complexVariable1',

};

let complexVariable2 = complexVariable1;

// 打印出兩個(gè)變量的值,沒有什么問題

console.log(complexVariable1);  // name:complexVariable1

console.log(complexVariable2);  // name:complexVariable1

// 修改complexVariable2中的值颁虐,一切就不如預(yù)期了:complexVariable1中的值也發(fā)生了變化

complexVariable2.name = 'complexVariable2';

console.log(complexVariable1);  // name:complexVariable2

console.log(complexVariable2);  // name:complexVariable2

本文基于以上代碼反映的問題對(duì)基本類型和引用類型從內(nèi)存空間的角度做深入學(xué)習(xí)蛮原,從而對(duì)變量的聲明、引用過程有更深的理解另绩,間接達(dá)到提升代碼質(zhì)量的作用儒陨。

《JavaScript高級(jí)程序設(shè)計(jì)》中包含的這些內(nèi)容的章節(jié):4.1基本類型和引用類型的值

參考文章:

MDN-內(nèi)存管理

Does JavaScript use stack or heap for memory allocation or both?

stack and heap in V8

前端基礎(chǔ)進(jìn)階(一):內(nèi)存空間詳細(xì)圖解

國(guó)內(nèi)外已經(jīng)有很多大神寫了相關(guān)的文章或回答,本文基于這些文章學(xué)習(xí)理解而得笋籽。簡(jiǎn)書上關(guān)于 內(nèi)存空間詳細(xì)圖解 的這篇文章已經(jīng)寫得非常詳盡蹦漠,因此本文僅對(duì)那篇文章中沒有提及或不夠完善的部分做補(bǔ)充。

棧內(nèi)存與堆內(nèi)存

棧內(nèi)存作為內(nèi)存中的一塊區(qū)域车海,遵循先進(jìn)后出First-In-Last-Out的存儲(chǔ)模式津辩。一般棧內(nèi)存比較小,但是存取數(shù)據(jù)的效率遠(yuǎn)高于堆內(nèi)存,因此多用來存儲(chǔ)一些簡(jiǎn)單的數(shù)據(jù)喘沿。

堆內(nèi)存和棧內(nèi)存正好相反闸度,內(nèi)存大但是存取數(shù)據(jù)的效率低,數(shù)據(jù)存儲(chǔ)方式無序且隨意蚜印,因此適合存放一些復(fù)雜的數(shù)據(jù)結(jié)構(gòu)莺禁。

通過這一小段簡(jiǎn)單的說明,其實(shí)對(duì)ES中的數(shù)據(jù)類型可以有一些合理的猜想:

因?yàn)闂?nèi)存空間小存取速度快窄赋,多用來存儲(chǔ)一些簡(jiǎn)單的數(shù)據(jù)哟冬,那么ES中基本類型值很可能就存儲(chǔ)棧內(nèi)存中;

而因?yàn)槎褍?nèi)存的一些特性忆绰,使得它非常適合拿來存儲(chǔ)引用類型值浩峡。

在接下來章節(jié)中我們會(huì)去探究到底是不是這種分配方式。

ES中的內(nèi)存空間

ECMA-262標(biāo)準(zhǔn)中沒有定義內(nèi)存布局错敢,因此本文討論的內(nèi)存空間是由JavaScript引擎實(shí)現(xiàn)的翰灾。

這里以V8引擎的內(nèi)存布局實(shí)現(xiàn)舉例。

從js垃圾回收機(jī)制來認(rèn)識(shí)它的內(nèi)存空間

參考文章Does JavaScript use stack or heap for memory allocation or both?中提到:

The easiest way is to think for a second. Javascript is a garbage-collected language. GC means that there is data scattered around which has to be cleaned up. Does that sound like the data is stored on the stack? Rather... no, because the stack has strictly ordered data. So, variables are allocated on the heap.

MDN 內(nèi)存管理中也有提到:

像C語言這樣的高級(jí)語言一般都有底層的內(nèi)存管理接口稚茅,比如 malloc()和free()纸淮。另一方面,JavaScript創(chuàng)建變量(對(duì)象亚享,字符串等)時(shí)分配內(nèi)存咽块,并且在不再使用它們時(shí)“自動(dòng)”釋放。 后一個(gè)過程稱為垃圾回收欺税。這個(gè)“自動(dòng)”是混亂的根源侈沪,并讓JavaScript(和其他高級(jí)語言)開發(fā)者感覺他們可以不關(guān)心內(nèi)存管理。 這是錯(cuò)誤的晚凿。

JavaScript的垃圾回收機(jī)制意味著它會(huì)去不斷回收雜亂無序的不再使用的變量亭罪,而堆內(nèi)存嚴(yán)格的先進(jìn)后出的存儲(chǔ)模式無法滿足垃圾回收機(jī)制的運(yùn)行要求--通過這點(diǎn)可以看出JavaScript中的變量都是存儲(chǔ)在堆內(nèi)存中的。

結(jié)論

既然已經(jīng)得出了結(jié)論那么為什么還有之后那么多章節(jié)晃虫,而且之后的章節(jié)中還提到了棧內(nèi)存皆撩,這和得出的結(jié)論是否矛盾呢?在翻了一些文章結(jié)合理解之后得出了如下較為系統(tǒng)的結(jié)論:

  • ES的標(biāo)準(zhǔn)中沒有明確定義內(nèi)存布局哲银,內(nèi)存布局由具體的JavaScript引擎實(shí)現(xiàn)扛吞。

  • 通過內(nèi)存回收機(jī)制可以看出JavaScript中的變量都是存儲(chǔ)在堆內(nèi)存中的

  • 在之后章節(jié)中提到的棧內(nèi)存荆责,是由js引擎在堆內(nèi)存中模擬的一個(gè)類似于棧內(nèi)存的對(duì)象滥比。

內(nèi)存空間總覽

image

↑↑↑圖片來自內(nèi)存空間詳細(xì)圖解

基本類型的內(nèi)存空間

如上一節(jié)中猜測(cè)的一般:對(duì)于基本類型,其值直接存放在棧內(nèi)存中做院,我們可以直接通過變量名訪問到它們的值盲泛。因此書本中說

基本數(shù)據(jù)類型是按值訪問的濒持,因?yàn)榭梢圆僮鞅4嬖谧兞恐械膶?shí)際的值。


// 聲明一個(gè)基本類型的變量

const simpleVariable = 'simpleVariable';

// 由simpleVariable復(fù)制一個(gè)copySimpleVariable變量

let copySimpleVariable = simpleVariable;

// 因?yàn)榛绢愋偷淖兞渴前粗翟L問的寺滚,所以修改copySimpleVariable的值會(huì)改了copySimpleVariable的值不會(huì)對(duì)simpleVariable產(chǎn)生影響

copySimpleVariable = 123;

引用類型的內(nèi)存空間

而對(duì)于引用類型來說柑营,它們的值存放在堆內(nèi)存中,變量名保存的是一個(gè)內(nèi)存地址村视,當(dāng)我們?cè)L問這個(gè)變量的時(shí)候?qū)嶋H訪問的是一個(gè)引用地址官套,通過這個(gè)引用地址訪問到存在在堆內(nèi)存中的值。


//聲明一個(gè)引用類型

const m = {

name: 'complexVariable',

};

//聲明一個(gè)新的引用類型蚁孔,復(fù)制自complexVariable

const n = complexVariable;

如這段代碼所示奶赔,當(dāng)我們聲明一個(gè)復(fù)制自m的引用類型n時(shí),并不是像基本類型一般會(huì)直接修改這個(gè)變量的值杠氢,而是給這個(gè)變量一個(gè)指向堆內(nèi)存的引用地址的值站刑,如圖所示:

image

↑↑↑圖片來自內(nèi)存空間詳細(xì)圖解

當(dāng)操作引用類型值時(shí)內(nèi)存空間發(fā)生了什么變化

對(duì)引用類型的操作大體有如下幾種:

  • 復(fù)制

  • 增加、刪除鼻百、修改屬性

  • 賦值


當(dāng)發(fā)生復(fù)制操作時(shí)绞旅,內(nèi)存空間如上節(jié)中提到的所示,當(dāng)我們聲明一個(gè)復(fù)制自 m的引用類型 n時(shí)愕宋,并不是像基本類型一般會(huì)直接修改這個(gè)變量的值玻靡,而是給這個(gè)變量一個(gè)指向堆內(nèi)存的引用地址的值结榄。此時(shí)發(fā)生的情況是書本中說的

引用類型的值是按引用訪問的中贝。


當(dāng)發(fā)生增加、刪除臼朗、修改屬性時(shí)邻寿,會(huì)通過變量的內(nèi)存地址的值訪問到在堆內(nèi)存中實(shí)際的對(duì)象,并對(duì)這個(gè)實(shí)際的對(duì)象進(jìn)行相應(yīng)的增加视哑、刪除绣否、修改屬性操作。此時(shí)的情況便是在書本下方加的注解

但在為對(duì)象添加屬性時(shí)挡毅,操作的是實(shí)際的對(duì)象蒜撮。

正因?yàn)槭遣僮髁藢?shí)際的對(duì)象,所以會(huì)導(dǎo)致所有指向這個(gè)堆內(nèi)存中對(duì)象的變量都發(fā)生變化跪呈,這就是文章開頭demo中說到的情況:

聲明兩個(gè)復(fù)雜類型的變量段磨,complexVariable2復(fù)制自complexVariable1。

修改complexVariable2中的值耗绿,一切就不如預(yù)期了:complexVariable1中的值也發(fā)生了變化


當(dāng)發(fā)生賦值操作時(shí)苹支,操作對(duì)象的值會(huì)賦值為堆內(nèi)存中將要賦值的對(duì)象的內(nèi)存地址。而此時(shí)就算操作的變量是復(fù)制自別的引用類型變量误阻,也會(huì)接觸兩個(gè)變量之間的關(guān)系债蜜,無論操作哪個(gè)對(duì)象都不會(huì)對(duì)另一個(gè)產(chǎn)生影響:


// 聲明一個(gè)引用類型變量

let complexVariable1 = {

name: 'complexVariable1',

code: '01',

};

// 聲明一個(gè)復(fù)制自complexVariable1的變量

let complexVariable2 = complexVariable1;

// 修改complexVariable2的name屬性晴埂,會(huì)操作在堆內(nèi)存中的實(shí)際對(duì)象導(dǎo)致complexVariable1也發(fā)生變化

complexVariable2.name = 'complexVariable2';

console.log(complexVariable1.name);  // complexVariable2

// 而當(dāng)我們做賦值操作的時(shí)候,會(huì)將一個(gè)新的內(nèi)存地址賦值給complexVariable2

complexVariable2 = {

name: 'new complexVariable2',

code: '007',

}

// 現(xiàn)在complexVariable2和complexVariable1已經(jīng)沒有半毛錢關(guān)系了

complexVariable2.name = 'change back to complexVariable1';

console.log(complexVariable1.name);  // complexVariable2


通過對(duì)內(nèi)存空間的學(xué)習(xí)寻定,可以清楚地知道變量發(fā)生創(chuàng)建賦值修改操作時(shí)js引擎做了哪些動(dòng)作儒洛,從而更好地把握并使用各個(gè)變量。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末狼速,一起剝皮案震驚了整個(gè)濱河市晶丘,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌唐含,老刑警劉巖浅浮,帶你破解...
    沈念sama閱讀 219,427評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異捷枯,居然都是意外死亡滚秩,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門淮捆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來郁油,“玉大人,你說我怎么就攤上這事攀痊⊥╇纾” “怎么了?”我有些...
    開封第一講書人閱讀 165,747評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵苟径,是天一觀的道長(zhǎng)案站。 經(jīng)常有香客問我,道長(zhǎng)棘街,這世上最難降的妖魔是什么蟆盐? 我笑而不...
    開封第一講書人閱讀 58,939評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮遭殉,結(jié)果婚禮上石挂,老公的妹妹穿的比我還像新娘。我一直安慰自己险污,他們只是感情好痹愚,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,955評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著蛔糯,像睡著了一般拯腮。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上渤闷,一...
    開封第一講書人閱讀 51,737評(píng)論 1 305
  • 那天疾瓮,我揣著相機(jī)與錄音,去河邊找鬼飒箭。 笑死狼电,一個(gè)胖子當(dāng)著我的面吹牛蜒灰,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播肩碟,決...
    沈念sama閱讀 40,448評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼强窖,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了削祈?” 一聲冷哼從身側(cè)響起翅溺,我...
    開封第一講書人閱讀 39,352評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎髓抑,沒想到半個(gè)月后咙崎,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,834評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡吨拍,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,992評(píng)論 3 338
  • 正文 我和宋清朗相戀三年褪猛,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片羹饰。...
    茶點(diǎn)故事閱讀 40,133評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡伊滋,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出队秩,到底是詐尸還是另有隱情笑旺,我是刑警寧澤,帶...
    沈念sama閱讀 35,815評(píng)論 5 346
  • 正文 年R本政府宣布馍资,位于F島的核電站筒主,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏迷帜。R本人自食惡果不足惜物舒,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,477評(píng)論 3 331
  • 文/蒙蒙 一色洞、第九天 我趴在偏房一處隱蔽的房頂上張望戏锹。 院中可真熱鬧,春花似錦火诸、人聲如沸锦针。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽奈搜。三九已至,卻和暖如春盯荤,著一層夾襖步出監(jiān)牢的瞬間馋吗,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工秋秤, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留宏粤,地道東北人脚翘。 一個(gè)月前我還...
    沈念sama閱讀 48,398評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像绍哎,于是被迫代替她去往敵國(guó)和親来农。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,077評(píng)論 2 355