摘要:一棧數(shù)據(jù)結(jié)構(gòu)與不同,中并沒(méi)有嚴(yán)格意義上區(qū)分棧內(nèi)存與堆內(nèi)存。引用數(shù)據(jù)類型的值是保存在堆內(nèi)存中的對(duì)象。不允許直接訪問(wèn)堆內(nèi)存中的位置,因此我們不能直接操作對(duì)象的堆內(nèi)存空間。為了更好的搞懂變量對(duì)象與堆內(nèi)存,我們可以結(jié)合以下例子與圖解進(jìn)行理解。
var a = 20; var b = "abc"; var c = true; var d = { m: 20 }
因?yàn)镴avaScript具有自動(dòng)垃圾回收機(jī)制,所以對(duì)于前端開(kāi)發(fā)來(lái)說(shuō),內(nèi)存空間并不是一個(gè)經(jīng)常被提及的概念,很容易被大家忽視。特別是很多不是計(jì)算機(jī)專業(yè)的朋友在進(jìn)入到前端之后,會(huì)對(duì)內(nèi)存空間的認(rèn)知比較模糊,甚至有些人干脆就是一無(wú)所知。
當(dāng)然也包括我自己。在很長(zhǎng)一段時(shí)間里認(rèn)為內(nèi)存空間的概念在JS的學(xué)習(xí)中并不是那么重要。可是后我當(dāng)我回過(guò)頭來(lái)重新整理JS基礎(chǔ)時(shí),發(fā)現(xiàn)由于對(duì)它們的模糊認(rèn)知,導(dǎo)致了很多東西我都理解得并不明白。比如最基本的引用數(shù)據(jù)類型和引用傳遞到底是怎么回事兒?比如淺復(fù)制與深復(fù)制有什么不同?還有閉包,原型等等。
因此后來(lái)我才漸漸明白,想要對(duì)JS的理解更加深刻,就必須對(duì)內(nèi)存空間有一個(gè)清晰的認(rèn)知。
在學(xué)習(xí)內(nèi)存空間之前,我們需要對(duì)三種數(shù)據(jù)結(jié)構(gòu)有一個(gè)直觀的認(rèn)知。他們分別是堆(heap),棧(stack)與隊(duì)列(queue)。
與C/C++不同,JavaScript中并沒(méi)有嚴(yán)格意義上區(qū)分棧內(nèi)存與堆內(nèi)存。因此我們可以簡(jiǎn)單粗暴的理解為JavaScript的所有數(shù)據(jù)都保存在堆內(nèi)存中。但是在某些場(chǎng)景,我們?nèi)匀恍枰诙褩?shù)據(jù)結(jié)構(gòu)的思維來(lái)實(shí)現(xiàn)一些功能,比如JavaScript的執(zhí)行上下文(關(guān)于執(zhí)行上下文我會(huì)在下一篇文章中總結(jié))。執(zhí)行上下文的執(zhí)行順序借用了棧數(shù)據(jù)結(jié)構(gòu)的存取方式。(也就是后面我們會(huì)經(jīng)常提到的函數(shù)調(diào)用棧)。因此理解棧數(shù)據(jù)結(jié)構(gòu)的原理與特點(diǎn)十分重要。
要簡(jiǎn)單理解棧的存取方式,我們可以通過(guò)類比乒乓球盒子來(lái)分析。如下圖左側(cè)。
這種乒乓球的存放方式與棧中存取數(shù)據(jù)的方式如出一轍。處于盒子中最頂層的乒乓球5,它一定是最后被放進(jìn)去,但可以最先被使用。而我們想要使用底層的乒乓球1,就必須將上面的4個(gè)乒乓球取出來(lái),讓乒乓球1處于盒子頂層。這就是棧空間先進(jìn)后出,后進(jìn)先出的特點(diǎn)。圖中已經(jīng)詳細(xì)的表明了棧空間的存儲(chǔ)原理。
堆數(shù)據(jù)結(jié)構(gòu)是一種樹(shù)狀結(jié)構(gòu)。它的存取數(shù)據(jù)的方式,則與書架與書非常相似。
書雖然也整齊的存放在書架上,但是我們只要知道書的名字,我們就可以很方便的取出我們想要的書,而不用像從乒乓球盒子里取乒乓一樣,非得將上面的所有乒乓球拿出來(lái)才能取到中間的某一個(gè)乒乓球。好比在JSON格式的數(shù)據(jù)中,我們存儲(chǔ)的key-value是可以無(wú)序的,因?yàn)轫樞虻牟煌⒉挥绊懳覀兊氖褂茫覀冎恍枰P(guān)心書的名字。
三、隊(duì)列在JavaScript中,理解隊(duì)列數(shù)據(jù)結(jié)構(gòu)的目的主要是為了清晰的明白事件循環(huán)(Event Loop)的機(jī)制到底是怎么回事。在后續(xù)的章節(jié)中我會(huì)詳細(xì)分析事件循環(huán)機(jī)制。
隊(duì)列是一種先進(jìn)先出(FIFO)的數(shù)據(jù)結(jié)構(gòu)。正如排隊(duì)過(guò)安檢一樣,排在隊(duì)伍前面的人一定是最先過(guò)檢的人。用以下的圖示可以清楚的理解隊(duì)列的原理。
JavaScript的執(zhí)行上下文生成之后,會(huì)創(chuàng)建一個(gè)叫做變量對(duì)象的特殊對(duì)象(具體會(huì)在下一篇文章與執(zhí)行上下文一起總結(jié)),JavaScript的基礎(chǔ)數(shù)據(jù)類型往往都會(huì)保存在變量對(duì)象中。
嚴(yán)格意義上來(lái)說(shuō),變量對(duì)象也是存放于堆內(nèi)存中,但是由于變量對(duì)象的特殊職能,我們?cè)诶斫鈺r(shí)仍然需要將其于堆內(nèi)存區(qū)分開(kāi)來(lái)。
基礎(chǔ)數(shù)據(jù)類型都是一些簡(jiǎn)單的數(shù)據(jù)段,JavaScript中有5中基礎(chǔ)數(shù)據(jù)類型,分別是Undefined、Null、Boolean、Number、String。基礎(chǔ)數(shù)據(jù)類型都是按值訪問(wèn),因?yàn)槲覀兛梢灾苯硬僮鞅4嬖谧兞恐械膶?shí)際的值。
ES6中新加了一種基礎(chǔ)數(shù)據(jù)類型Symbol,可以先不用考慮他
與其他語(yǔ)言不同,JS的引用數(shù)據(jù)類型,比如數(shù)組Array,它們值的大小是不固定的。引用數(shù)據(jù)類型的值是保存在堆內(nèi)存中的對(duì)象。JavaScript不允許直接訪問(wèn)堆內(nèi)存中的位置,因此我們不能直接操作對(duì)象的堆內(nèi)存空間。在操作對(duì)象時(shí),實(shí)際上是在操作對(duì)象的引用而不是實(shí)際的對(duì)象。因此,引用類型的值都是按引用訪問(wèn)的。這里的引用,我們可以理解為保存在變量對(duì)象中的一個(gè)地址,該地址與堆內(nèi)存的實(shí)際值相關(guān)聯(lián)。
為了更好的搞懂變量對(duì)象與堆內(nèi)存,我們可以結(jié)合以下例子與圖解進(jìn)行理解。
var a1 = 0; // 變量對(duì)象 var a2 = "this is string"; // 變量對(duì)象 var a3 = null; // 變量對(duì)象 var b = { m: 20 }; // 變量b存在于變量對(duì)象中,{m: 20} 作為對(duì)象存在于堆內(nèi)存中 var c = [1, 2, 3]; // 變量c存在于變量對(duì)象中,[1, 2, 3] 作為對(duì)象存在于堆內(nèi)存中
因此當(dāng)我們要訪問(wèn)堆內(nèi)存中的引用數(shù)據(jù)類型時(shí),實(shí)際上我們首先是從變量對(duì)象中獲取了該對(duì)象的地址引用(或者地址指針),然后再?gòu)亩褍?nèi)存中取得我們需要的數(shù)據(jù)。
理解了JS的內(nèi)存空間,我們就可以借助內(nèi)存空間的特性來(lái)驗(yàn)證一下引用類型的一些特點(diǎn)了。
在前端面試中我們常常會(huì)遇到這樣一個(gè)類似的題目
// demo01.js var a = 20; var b = a; b = 30; // 這時(shí)a的值是多少?
// demo02.js var m = { a: 10, b: 20 } var n = m; n.a = 15; // 這時(shí)m.a的值是多少
在變量對(duì)象中的數(shù)據(jù)發(fā)生復(fù)制行為時(shí),系統(tǒng)會(huì)自動(dòng)為新的變量分配一個(gè)新值。var b = a執(zhí)行之后,a與b雖然值都等于20,但是他們其實(shí)已經(jīng)是相互獨(dú)立互不影響的值了。具體如圖。所以我們修改了b的值以后,a的值并不會(huì)發(fā)生變化。
在demo02中,我們通過(guò)var n = m執(zhí)行一次復(fù)制引用類型的操作。引用類型的復(fù)制同樣也會(huì)為新的變量自動(dòng)分配一個(gè)新的值保存在變量對(duì)象中,但不同的是,這個(gè)新的值,僅僅只是引用類型的一個(gè)地址指針。當(dāng)?shù)刂分羔樝嗤瑫r(shí),盡管他們相互獨(dú)立,但是在變量對(duì)象中訪問(wèn)到的具體對(duì)象實(shí)際上是同一個(gè)。如圖所示。
因此當(dāng)我改變n時(shí),m也發(fā)生了變化。這就是引用類型的特性。
通過(guò)內(nèi)存的角度來(lái)理解,是不是感覺(jué)要輕松很多?除此之外,我們還可以以此為基礎(chǔ),一步一步的理解JavaScript的執(zhí)行上下文,作用域鏈,閉包,原型鏈等重要概念。其他的我會(huì)在以后的文章慢慢總結(jié),敬請(qǐng)期待。
因?yàn)镴avaScript具有自動(dòng)垃圾收集機(jī)制,所以我們?cè)陂_(kāi)發(fā)時(shí)好像不用關(guān)心內(nèi)存的使用問(wèn)題,內(nèi)存的分配與回收都完全實(shí)現(xiàn)了自動(dòng)管理。但是根據(jù)我自己的開(kāi)發(fā)經(jīng)驗(yàn),了解內(nèi)存機(jī)制有助于自己清晰的認(rèn)識(shí)到自己寫的代碼在執(zhí)行過(guò)程中發(fā)生過(guò)什么,從而寫出性能更加優(yōu)秀的代碼。因此關(guān)心內(nèi)存是一件非常重要的事情。
JavaScript的內(nèi)存生命周期
1. 分配你所需要的內(nèi)存 2. 使用分配到的內(nèi)存(讀、寫) 3. 不需要時(shí)將其釋放、歸還
為了便于理解,我們使用一個(gè)簡(jiǎn)單的例子來(lái)解釋這個(gè)周期。
var a = 20; // 在內(nèi)存中給數(shù)值變量分配空間 alert(a + 100); // 使用內(nèi)存 a = null; // 使用完畢之后,釋放內(nèi)存空間
第一步和第二步我們都很好理解,JavaScript在定義變量時(shí)就完成了內(nèi)存分配。第三步釋放內(nèi)存空間則是我們需要重點(diǎn)理解的一個(gè)點(diǎn)。
JavaScript有自動(dòng)垃圾收集機(jī)制,那么這個(gè)自動(dòng)垃圾收集機(jī)制的原理是什么呢?其實(shí)很簡(jiǎn)單,就是找出那些不再繼續(xù)使用的值,然后釋放其占用的內(nèi)存。垃圾收集器會(huì)每隔固定的時(shí)間段就執(zhí)行一次釋放操作。
在JavaScript中,最常用的是通過(guò)標(biāo)記清除的算法來(lái)找到哪些對(duì)象是不再繼續(xù)使用的,因此a = null其實(shí)僅僅只是做了一個(gè)釋放引用的操作,讓 a 原本對(duì)應(yīng)的值失去引用,脫離執(zhí)行環(huán)境,這個(gè)值會(huì)在下一次垃圾收集器執(zhí)行操作時(shí)被找到并釋放。而在適當(dāng)?shù)臅r(shí)候解除引用,是為頁(yè)面獲得更好性能的一個(gè)重要方式。
在局部作用域中,當(dāng)函數(shù)執(zhí)行完畢,局部變量也就沒(méi)有存在的必要了,因此垃圾收集器很容易做出判斷并回收。但是全局變量什么時(shí)候需要自動(dòng)釋放內(nèi)存空間則很難判斷,因此在我們的開(kāi)發(fā)中,需要盡量避免使用全局變量。
要詳細(xì)了解垃圾收集機(jī)制,建議閱讀《JavaScript高級(jí)編程》中的4.3節(jié)
前端基礎(chǔ)進(jìn)階系列目錄
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/90524.html
摘要:進(jìn)階期理解中的執(zhí)行上下文和執(zhí)行棧進(jìn)階期深入之執(zhí)行上下文棧和變量對(duì)象但是今天補(bǔ)充一個(gè)知識(shí)點(diǎn)某些情況下,調(diào)用堆棧中函數(shù)調(diào)用的數(shù)量超出了調(diào)用堆棧的實(shí)際大小,瀏覽器會(huì)拋出一個(gè)錯(cuò)誤終止運(yùn)行。 (關(guān)注福利,關(guān)注本公眾號(hào)回復(fù)[資料]領(lǐng)取優(yōu)質(zhì)前端視頻,包括Vue、React、Node源碼和實(shí)戰(zhàn)、面試指導(dǎo)) 本周正式開(kāi)始前端進(jìn)階的第一期,本周的主題是調(diào)用堆棧,今天是第3天。 本計(jì)劃一共28期,每期重點(diǎn)攻...
摘要:不過(guò)其實(shí)簡(jiǎn)書文章評(píng)論里有很多大家的問(wèn)題以及解答,對(duì)于進(jìn)一步理解文中知識(shí)幫助很大的,算是有點(diǎn)可惜吧。不過(guò)也希望能夠?qū)φ趯W(xué)習(xí)前端的你有一些小幫助。如果在閱讀中發(fā)現(xiàn)了一些錯(cuò)誤,請(qǐng)?jiān)谠u(píng)論里告訴我,我會(huì)及時(shí)更改。 前端基礎(chǔ)進(jìn)階(一):內(nèi)存空間詳細(xì)圖解 前端基礎(chǔ)進(jìn)階(二):執(zhí)行上下文詳細(xì)圖解 前端基礎(chǔ)進(jìn)階(三):變量對(duì)象詳解 前端基礎(chǔ)進(jìn)階(四):詳細(xì)圖解作用域鏈與閉包 前端基礎(chǔ)進(jìn)階(五):全方位...
摘要:之前一篇文章我們?cè)敿?xì)說(shuō)明了變量對(duì)象,而這里,我們將詳細(xì)說(shuō)明作用域鏈。而的作用域鏈,則同時(shí)包含了這三個(gè)變量對(duì)象,所以的執(zhí)行上下文可如下表示。下圖展示了閉包的作用域鏈。其中為當(dāng)前的函數(shù)調(diào)用棧,為當(dāng)前正在被執(zhí)行的函數(shù)的作用域鏈,為當(dāng)前的局部變量。 showImg(https://segmentfault.com/img/remote/1460000008329355);初學(xué)JavaScrip...
摘要:文章分享持續(xù)更新更多資源請(qǐng)文章轉(zhuǎn)自一前端文章基礎(chǔ)篇,,前端基礎(chǔ)進(jìn)階一內(nèi)存空間詳細(xì)圖解前端基礎(chǔ)進(jìn)階二執(zhí)行上下文詳細(xì)圖解前端基礎(chǔ)進(jìn)階三變量對(duì)象詳解前端基礎(chǔ)進(jìn)階四詳細(xì)圖解作用域鏈與閉包前端基礎(chǔ)進(jìn)階五全方位解讀前端基礎(chǔ)進(jìn)階六在開(kāi)發(fā)者工具中觀察函數(shù)調(diào) 文章分享(持續(xù)更新) 更多資源請(qǐng)Star:https://github.com/maidishike... 文章轉(zhuǎn)自:https://gith...
摘要:引擎對(duì)堆內(nèi)存中的對(duì)象進(jìn)行分代管理新生代存活周期較短的對(duì)象,如臨時(shí)變量字符串等。內(nèi)存泄漏對(duì)于持續(xù)運(yùn)行的服務(wù)進(jìn)程,必須及時(shí)釋放不再用到的內(nèi)存。 (關(guān)注福利,關(guān)注本公眾號(hào)回復(fù)[資料]領(lǐng)取優(yōu)質(zhì)前端視頻,包括Vue、React、Node源碼和實(shí)戰(zhàn)、面試指導(dǎo)) 本周正式開(kāi)始前端進(jìn)階的第一期,本周的主題是調(diào)用堆棧,今天是第4天。 本計(jì)劃一共28期,每期重點(diǎn)攻克一個(gè)面試重難點(diǎn),如果你還不了解本進(jìn)階計(jì)劃...
閱讀 1309·2021-09-27 13:56
閱讀 2338·2019-08-26 10:35
閱讀 3497·2019-08-23 15:53
閱讀 1848·2019-08-23 14:42
閱讀 1233·2019-08-23 14:33
閱讀 3562·2019-08-23 12:36
閱讀 1947·2019-08-22 18:46
閱讀 996·2019-08-22 14:06