JS的數據類型已是你們都很熟悉的東西了,可是你們是否對這些數據類型在內存中的分配了解,甚至在操做這些變量時,內存中是如何表現的,本文將對這些作一個總結。javascript
ECMAScript 變量可能包含兩種不一樣數據類型的值:基本類型值和引用類型值。在將一個值賦給變量時,解析器必須肯定這個值是基本類型值仍是引用類型值。vue
基本類型值 指的是簡單的數據段,如: Undefined、Null、Boolean、Number 和 String。這 5 種基本數據類型是按值訪問的,由於能夠操做保存在變量中的實際的值。java
引用類型值指 那些可能由多個值構成的對象,而且引用類型的值是保存在內存中的對象,如: Object、Array、Function、Date對象等。與其餘語言不一樣,JavaScript 不容許直接訪問內存中的位置, 也就是說不能直接操做對象的內存空間。在操做對象時,其實是在操做對象的引用而不是實際的對象。 爲此,引用類型的值是按引用訪問的。react
要說明這個問題須要先來了解一下棧內存和堆內存。
基本類型值是存儲在棧中的簡單數據段,也就是說,他們的值直接存儲在變量訪問的位置。
堆是存放數據的基於散列算法的數據結構,在javascript中,引用值是存放在堆中的。c++
如如下代碼:程序員
const a = 1; const obj1 = { name: '小明', age: 18 } const obj2 = obj1
在內存中的表現爲:
算法
在變量聲明時,基本類型變量會直接在棧內存中爲它分配空間,變量值也是直接存儲在棧內存中;
而引用類型變量的真實值是存儲在堆內存中的,同時棧內存中會保存一個指針,這個指針指向真實值在對內存中的位置,也以理解爲棧內存中存放了引用類型值存儲的地址。
因此訪問基本類型的變量時,是直接訪問到棧內存中其真正的值;而訪問引用類型的變量時,是經過棧內存中保存的引用地址去訪問。因此在上例中,從新聲明瞭一個obj2,並將obj1賦給obj2 實際上是將obj1存放的引用賦給了obj2,此時obj1和obj2的指針指向了堆內存中的同一個對象,那麼咱們更改obj1的name屬性時obj2會變化,一樣更改obj2的name屬性時obj1也會變化。那麼此時若是新聲明一個變量b = a會怎樣呢,由於是基本類型各不影響。redux
棧的優點就是存取速度比堆要快,僅次於直接位於CPU中的寄存器,但缺點是,存在棧中的數據大小與生存期必須是肯定的,缺少靈活性。堆的優點是能夠動態地分配內存大小,生存期也沒必要事先告訴編譯器,垃圾收集器會自動地收走這些再也不使用的數據,可是缺點是因爲在運行時動態分配內存,因此存取速度較慢。數組
因此相對於簡單數據類型而言,他們佔用內存比較小,若是放在堆中,查找會浪費不少時間,而把堆中的數據放入棧中也會影響棧的效率。好比對象和數組是能夠無限拓展的,正好放在能夠動態分配大小的堆中。瀏覽器
*注 : 如下爲c++中,對內存與棧內存的區別,不少地方相通,可輔助理解
主要的區別由如下幾點: 一、管理方式不一樣; 二、空間大小不一樣; 三、可否產生碎片不一樣; 四、生長方向不一樣; 五、分配方式不一樣; 六、分配效率不一樣; 管理方式:對於棧來說,是由編譯器自動管理,無需咱們手工控制;對於堆來講,釋放工做由程序員控制,容易產生memory leak。(js有本身的垃圾回收機制,此條不適用) 空間大小:通常來說在32位系統下,堆內存能夠達到4G的空間,從這個角度來看堆內存幾乎是沒有什麼限制的。可是對於棧來說,通常都是有必定的空間大小的,例如,在VC6下面,默認的棧空間大小是1M(好像是,記不清楚了)。固然,咱們能夠修改。 碎片問題:對於堆來說,頻繁的new/delete勢必會形成內存空間的不連續,從而形成大量的碎片,使程序效率下降。對於棧來說,則不會存在這個問題,由於棧是先進後出的隊列,他們是如此的一一對應,以致於永遠都不可能有一個內存塊從棧中間彈出,在他彈出以前,在他上面的後進的棧內容已經被彈出,詳細的能夠參考數據結構,這裏咱們就再也不一一討論了。 生長方向:對於堆來說,生長方向是向上的,也就是向着內存地址增長的方向;對於棧來說,它的生長方向是向下的,是向着內存地址減少的方向增加。 分配方式:堆都是動態分配的,沒有靜態分配的堆。棧有2種分配方式:靜態分配和動態分配。靜態分配是編譯器完成的,好比局部變量的分配。動態分配由alloca函數進行分配,可是棧的動態分配和堆是不一樣的,他的動態分配是由編譯器進行釋放,無需咱們手工實現。 分配效率:棧是機器系統提供的數據結構,計算機會在底層對棧提供支持:分配專門的寄存器存放棧的地址,壓棧出棧都有專門的指令執行,這就決定了棧的效率比較高。堆則是C/C++函數庫提供的,它的機制是很複雜的,例如爲了分配一塊內存,庫函數會按照必定的算法(具體的算法能夠參考數據結構/操做系統)在堆內存中搜索可用的足夠大小的空間,若是沒有足夠大小的空間(多是因爲內存碎片太多),就有可能調用系統功能去增長程序數據段的內存空間,這樣就有機會分到足夠大小的內存,而後進行返回。顯然,堆的效率比棧要低得多。 從這裏咱們能夠看到,堆和棧相比,因爲大量new/delete的使用,容易形成大量的內存碎片;因爲沒有專門的系統支持,效率很低;因爲可能引起用戶態和核心態的切換,內存的申請,代價變得更加昂貴。因此棧在程序中是應用最普遍的,就算是函數的調用也利用棧去完成,函數調用過程當中的參數,返回地址,EBP和局部變量都採用棧的方式存放。因此,咱們推薦你們儘可能用棧,而不是用堆。 雖然棧有如此衆多的好處,可是因爲和堆相比不是那麼靈活,有時候分配大量的內存空間,仍是用堆好一些。
以下例:
var lang = "Java"; lang = lang + "Script";
以上示例中的變量 lang 開始時包含字符串"Java"。而第二行代碼把 lang 的值從新定義爲"Java" 與"Script"的組合,即"JavaScript"。實現這個操做的過程以下:首先建立一個能容納 10 個字符的 新字符串,而後在這個字符串中填充"Java"和"Script",最後一步是銷燬原來的字符串"Java"和字 符串"Script",由於這兩個字符串已經沒用了。這個過程是在後臺發生的,而這也是在某些舊版本的瀏覽器(例如版本低於 1.0 的 Firefox、IE6 等)中拼接字符串時速度很慢的緣由所在。但這些瀏覽器後來的版本已經解決了這個低效率問題。
瀏覽器內核的底層優化尚不清楚,可是有人採用下面的方法進行優化,只看一下方法瞭解便可,由於瀏覽器內核優化以後效率已提升沒必要如此大費周章。提升效率的辦法是用數組的join函數:
如:
var str ; str = 'this is a string'; str = str + ',another string.'; var tempArr = [] ,src,res; src = 'this is a string'; tempArr.push(src); tempArr.push(',another string.'); res = tempArr.join('');
關於開發中須要注意的事項,主要是對於引用類型須要特別注意。
1、須要將原數據 存儲副本,並操做數據(如對請求回來的列表數據進行過濾顯示)。這是個很常見的問題,基本都遇到過這種狀況,應該已經有處理經驗。主要就是若是直接將原數據data賦值給新變量a,那麼本身操做a時data的數據也會受到影響。解決辦法就是避免簡單表面的進行存副本操做,應該存一個獨立的副本。若是數據源是數組結構就Array.prototype.slice.call(arr), 若是數據源是對象結構進行拷貝,至於深拷貝仍是淺拷貝看本身的須要。我的感受能夠造成一種編碼習慣,相似場景要操做這樣的數據時先存一個獨立的副本(固然也要看需求是否須要,自行權衡)。
2、在MVVM框架中會常常出現 如model的某項數據是層數較深的複雜結構時,更改了數據項下的某個值時,view並不更新,由於view model以爲本身沒有變化,沒有通知view。好比react-redux中,action觸發的reducer更新了store下某項深結構數據下的某個值,又好比vue中簡單更改data中數組的某一項,都是不會觸發view更新的。解決辦法react中可少用深結構數據儘可能淺,也能夠採用深拷貝更新store。vue是框架中作了hack處理,可以使用$apply解決,也能夠用官方點名的八大數組自帶方法處理。
本文總結了JS數據類型及其聲明賦值更新時在內存堆棧中的表現,能夠更深刻的理解這些數據類型。並在有更深理解以後來回顧開發中的常見問題,作出總結,歡迎補充。