復雜值vs原始值&&內存空間


寫在前面

     最近在讀《JavaScript啟示錄》,這本書不是JavaScript的詳盡的參考指南,但是把對象作為了解JavaScript的透鏡,受益匪淺。

     那么我們先來聊一下JavaScript的原始值(值類型)以及復雜值(引用類型),以及他們在內存空間中的存儲,關於他們你可能不清楚的一些事:

     我們先通過一個經典的面試題類型(並不是原題,我即興發揮)引出我們今天的主題:

  

  

我們已經看出他們的差別,在圖一:我們讓b = a,改變b的值,發現a並沒有改變。在圖二:我們讓d = c,通過d.name改變對象的name屬性,發現c.name也變化了。

事實上,原始值存儲在棧內存中,按值來訪問。復雜值(引用類型)在堆內存里面,按引用地址訪問;然后我們會想到局部變量和全局變量在內存中的存儲:

 

下面會具體介紹復雜值、原始值以及他們的一些特性與內存空間:

  

  1、原始值是非對象

我們老生常談的JavaScript五大基本的數據類型,null、undefined、number、string、boolean都被視為原始值,因為他們是不可細化的,本身是簡單的,不能表示由其他的值組成的值。

      這里需要注意的是:與使用字面量語法創建相反,在使用new關鍵字創建的String,Number,或Boolean值時,創建的實際上是一個復雜對象,此時已不在是原始值

  a、下面對原始值和原生JavaScript對象之間的差異進行了比較:

  

需要注意沒有使用new關鍵詞,從構造函數返回的字符、數字、布爾值 對比 使用字面量方法所創建的仍然不是對象。

  b、我們在來對比一下使用new關鍵字創建的構造函數:

  

除了new出來的Function()對象返回的是function,其他都是object,其實在JavaScript中對函數定義非常高,因此在引用類型中,typeof能檢測出函數的詳細類型。

上述代碼可以告訴我們:原始值不是對象,原始值的特殊之處是用於表示簡單值;

  2、原始值的賦值,存儲,比較方式

  a、原始值在“ 面值(face value)”中的存儲和操作,理解這一點非常重要,因為原始值是真實值的復制

  

這里的重點是,原始值是作為不可細化的值進行存儲和操作的,引用他們會轉移其值:這里的意思也就是原始值(值類型)在內存中每一個值都會存儲在對應的變量的中去,也就是一個真實值的”復制”

  b、原始值的比較采用值比較

我們通過比較原始值來確定其值在字面上是否相同

通過下面的代碼來理解“值比較“的概念,並將它與復雜數字進行比較:

  

 

這里的重點是,在進行比較時,原始值會去檢查表示的值是否相等,這里我們要特別和復雜值進行比較(因為復雜值不會去比較值是否相等,而是比較引用地址是否相同)

3、原始值(String,Number,Boolean)在被用做對象時就像對象

null和undefined都是非常簡單的值,它們不需要構造函數,也沒有new操作為自己創建JavaScript值(可以把他們當做操作符來使用即可)

原始值被當做構造函數創建的一個對象來使用時(注意不使用new),JavaScript會把其轉化為一個對象,以便可以使用對象的特性(如方法),而拋棄對象的性質,並將它返回到原始值。

   

上述實例代碼,所有的原始值(除null、undefined)都被轉化為對象,以便充分利用toString()方法一旦調用和返回改方法,對象就會被轉換成對象值。這樣我相信我們能很好的理解標題了

4、復雜值(復合對象、引用類型)

本質上,復雜對象其在內存中的大小是未知的,因為復雜對象可以包含任何值:

下面通過字面量的方法創建一個對象和數組

  

相比簡單的原始值,原始值不能表示復雜值,而復雜值可以封裝任意的JavaScript值

5、如何存儲或復制復雜值

復雜值是通過引用來進行存儲和操作的,這就回到了開始那個問題的圖二,理解這一點非常重要。創建一個包含復雜對象的變量時,其值是內存中的一個引用地址。引用一個復雜對象時,使用它的名稱(即變量或對象屬性)通過內存中的引用地址獲取對象值。當我們試圖復制一個復雜值的時候,理解這就非常重要了。復雜值復制的過程、其實並不是復制對象,更多的是像復制對象的地址

  

所以就像上面說過的,復制的是內存堆棧中對象的地址或者引用

6、復雜對象比較采用引用比較

也就是說:復雜對象只有在引用相同的對象(即有相同的引用地址)時才相等

   

我相信我們已經理解:指向內存中復雜對象的變量,只有在引用相同對的‘地址’的情況下才是相等的,相反,兩個單獨創建的對象、即使具有相同的類型並擁有完全相同的屬性,他們也是不相等的

7、復雜對象具有動態屬性

通過這一點,我們可以根據需求為復雜對象有任意多個引用。

  

上述代碼,objA、pointer1、pointer2都引用了內存中的同一對象

  

【emoji罒ω罒】這三個每次調用對象的方法都會叫他‘一個人’

  

復雜對象支持動態對象屬性,因為我們可以定義對象,然后創建引用,在更新對象、並且所有指向該對象的變量都會’獲得’更新.

8、動態屬性支持異變對象

復雜對象是由動態屬性構成的,這一點非常重要,這使得用戶自定義對象和大多數原生對象產生突變。通過增加原生對象、來改變JavaScript本身的原生預配置特性:

下面我們在原生構造函數上存儲屬性,並在原型對象上,向原生對象添加新方法:

  

所以我們明白,JavaScript的對象是動態的,這使得JavaScript對象是可變的。通過自定義我們改變了原生內部的運行機制,你會獲得一個自定義版本的JavaScript來處理程序,但是使用一定要謹慎。

9、兩個存儲空間:棧&&堆

 我們前面也提到了存儲空間,在程序運行時,有兩個存儲空間可用,一個是棧,歸屬進程本身的;另一個是堆,所有進程共用的

     然后就很好理解了,因為局部變量聲明在函數周期內部,在函數結束時其生命周期也就結束了,其存儲空間位於棧中,當進入函數時,會根據函數內部需求,在棧申請一段內存空間,供局部變量使用。當局部變量生命周期結束后(該函數結束),在棧上釋放。

     由於進程棧的空間是有限的,所以1)要避免申請占用空間較大的局部變量,2)避免函數嵌套層數過多,這些都可能引起棧空間不夠導致程序崩潰。

   關於數據結構中棧和堆,后面還會進一步的學習總結,比如管理方式、申請大小、碎片問題、分配方式、分配效率...

 寫在后面

相信到這里我們對js中的原始值、復雜值、以及他們的特性、在內存中的存儲有了比較深入的理解,那么讓我們開始准確我們的JavaScript世界觀系列,因為我從高中畢業后接觸前端,對原生的熱愛程度遠遠大於jQuery等類庫。如果想實現JavaScript類庫或者框架,應該打開“引擎蓋”看看,了解發動機的情況。

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM