2012-11-07 125 views
21

我讀了一本名爲「Professional Javascript for web developer」的書,它說:「變量由參考值或原始值分配,參考值是存儲在內存中的對象」。然後它沒有說明如何存儲Primitive值。所以我猜這不是存儲在內存中。基於這一點,當我有這樣的腳本:原始值vs參考值

var foo = 123; 

如何使用Javascript記住以後使用foo變量?

+5

聽起來像垃圾書。當然'foo'存儲在內存中。我想它試圖說參考值是*對象*存儲在內存中,但原始類型是*值*存儲在內存中? – MadSkunk

+0

@LacViet - 請閱讀我的答案。我相信它會更準確地回答您的問題:http://stackoverflow.com/a/13268731/783743 –

回答

41

A variable可以保存兩種值類型之一:primitive valuesreference values

  • Primitive values是存儲在堆棧上的數據。
  • Primitive value直接存儲在變量訪問的位置。
  • Reference values對象存儲在。存儲在變量位置中的
  • Reference value是指向存儲對象的存儲器中的位置的指針。
  • 原始類型包括UndefinedNullBooleanNumber,或String

基礎:

對象是屬性的聚合。酒店可以參考objectprimitivePrimitives are values,他們沒有任何屬性。

更新:

JavaScript有6元數據類型:布爾未定義符號(新中ES6)。除了空和未定義之外,所有基元值都具有包圍基元值的對象等價物,例如, a 字符串對象環繞字符串基元。所有基元都是不可變的。

+7

是的。但是基本類型包括:'string,number,boolean,null,undefined' - 帶有大寫字母「S」的'String'是'string'的對象包裝。請參閱:[MDN術語表 - 原語](https://developer.mozilla.org/en-US/docs/JavaScript/Glossary#primitive) – jfrej

+3

重要的區別在於,將參考值傳遞給函數時,函數修改它的內容,調用者和任何其他引用該對象的函數都會看到這種變化。 – Barmar

+0

@jfrej謝謝並更新 – Talha

2

在JavaScript中Primitive values數據存儲在stack

Primitive value直接存儲在變量訪問的位置。

Reference values對象存儲在heap

存儲在變量位置的參考值是指向存儲對象的內存位置的指針。

JavaScript支持五種基本數據類型:number, string, Boolean, undefined, and null

這些類型被稱爲原始類型,因爲它們是可以構建更復雜類型的基本構建塊。

在這五個中,只有number, string, and Boolean是實際存儲數據的實際數據類型。

Undefined and null是在特殊情況下出現的類型。 primitive type在內存中具有固定大小。例如,一個數字佔用八個字節的內存,而一個布爾值只能用一個位表示。

而引用類型可以是任意長度 - 它們沒有固定的大小。

+0

JavaScript中的布爾值佔用4個字節:https://stackoverflow.com/questions/4905861/memory-usage-of-different-data-types-in-javascript – Taurus

71

好吧,想象你的變量是一張紙 - 一個粘滯便箋。

注1:一個變量便條

現在,粘滯便箋非常小。你只能在它上面寫一點信息。如果你想寫更多的信息,你需要更多的便條,但這不是問題。想象一下,你有無窮無盡的便籤。

注2:您有一個無盡供應的即時貼,其中存儲少量信息

太棒了,你可以在粘滯便箋上寫什麼?我可以寫:

  1. 是或否(a 布爾值)。
  2. 我的年齡(a 號碼)。
  3. 我的名字(a 字符串)。
  4. 完全沒有(undefined)。
  5. 塗鴉或任何其他對我來說毫無意義的東西(null)。

因此,我們可以寫簡單的事情(讓我們的是居高臨下的,並呼籲他們原始事情)我們便籤。

注意3:你可以在你的便籤上寫原始東西。

所以說我在便條上寫30以提醒自己爲我晚上投擲我的地方(我有很少朋友)的小派對買了30片奶酪。

當我將粘滯便箋放在冰箱上時,我看到我的妻子在冰箱上貼了另一個便條,上面寫着30(提醒我說她的生日是在本月30號)。

問:這兩個粘滯便箋都傳達相同的信息?

- 答:是的,他們都說30。我們不知道這是30片奶酪還是本月的第30天,坦率地說我們不在乎。對於一個不瞭解情況的人來說,這一切都是一樣的。

var slicesOfCheese = 30; 
var wifesBirthdate = 30; 

alert(slicesOfCheese === wifesBirthdate); // true 

注4:其中兩個都寫上他們同樣的事情便籤傳達了相同的信息,即使它們是兩個不同的便籤。

今晚我真的很興奮 - 和老朋友閒逛,玩得很開心。然後我的一些朋友打電話給我,說他們不能參加派對。

因此,我去我的冰箱,並在我的便條上清除30(不是我妻子的便條 - 這會讓她非常生氣),並使其成爲20

注意5:您可以刪除粘滯便箋上寫的內容並寫下其他內容。

問:這一切都很好,但如果我的妻子想寫一份雜貨清單讓我在外出時買一些奶酪,該怎麼辦?她需要爲每件物品寫一張便條嗎?

A:不,她會在紙上寫一份較長的紙張清單並寫上雜貨清單。然後,她會寫一張便條,告訴我在哪裏可以找到雜貨清單。

那麼這裏發生了什麼?

  1. 雜貨名單顯然是不簡單(呃... 原始)數據。
  2. 我的妻子在一張較長的紙上寫下了它。
  3. 她寫在哪裏可以找到它的粘滯便箋。

親愛的,雜貨清單是在你的鍵盤下。

要回顧一下:

  1. 實際的對象(雜貨清單)是我的鍵盤下方。
  2. 粘滯便箋告訴我在哪裏可以找到它(物體的地址)。

註釋6:參考值是對象的引用(它們將被找到的地址)。

問:我們怎麼知道兩個粘滯便箋說同一件事的時候?假如我的妻子再次購買食品雜貨清單,以免我放錯了第一張,併爲它寫了另一張便條。這兩份清單都說了同樣的事情,但粘滯便箋說同樣的事情?

A:不是。第一個便籤告訴我們在哪裏可以找到第一個清單。第二個告訴我們在哪裏可以找到第二個列表。這兩個名單是否說同一件事並不重要。他們是兩個不同的名單。

var groceryList1 = ["1 dozen apples", "2 loaves of bread", "3 bottles of milk"]; 
var groceryList2 = ["1 dozen apples", "2 loaves of bread", "3 bottles of milk"]; 

alert(groceryList1 === groceryList2); // false 

注7:兩個便籤只傳達它們是否指同一個對象相同的信息。

這意味着,如果我的妻子做了兩張便箋,提醒我在購物清單的位置,那麼這兩張便箋包含相同的信息。所以這個:

親愛的,雜貨清單是在你的鍵盤下。

包含了相同的信息:

不要忘記,雜貨清單是你的鍵盤下方。

在編程方面:

var groceryList1 = ["1 dozen apples", "2 loaves of bread", "3 bottles of milk"]; 
var groceryList2 = groceryList1; 

alert(groceryList1 === groceryList2); // true 

所以這就是所有你需要知道的關於引用在JavaScript中。沒有必要進入像和動態內存分配的東西。如果您使用C/C++進行編程,這一點很重要。

編輯1:哦,最重要的事情是,當你通過你周圍的變量正在基本上通過原始按值參考參照

這是說,你要複製的一切寫在一個便籤到另一個(沒關係,無論您是複製原始值或參考)只是一個精心製作的方式。

複製引用時,被引用的對象不會移動(例如,我妻子的購物清單將始終保留在我的鍵盤下方,但我可以將粘貼的筆記複製到任何我想要的位置 - 原始便箋仍然會打開冰箱)。

編輯2:針對張貼@LacViet評論:

那麼對於初學者,我們正在談論的JavaScript和JavaScript沒有一個堆棧。這是一種動態語言,JavaScript中的所有變量都是動態的。爲了解釋這一差別我把它比作C.

考慮下面的C程序:

#include <stdio.h> 

int main() { 
    int a = 10; 
    int b = 20; 
    int c = a + b; 
    printf("%d", c); 
    return 0; 
} 

當我們編譯這個程序,我們得到一個可執行文件。可執行文件分爲多個段(或部分)。這些段包括堆棧段,代碼段,數據段,額外段等。

  1. 堆棧段用於在調用函數或中斷處理程序時存儲程序的狀態。例如,當函數f調用函數g時,函數f(當時寄存器中的所有值)的狀態被保存在堆棧中。當g將控制權返回至f時,這些值將被恢復。
  2. 代碼段包含處理器要執行的實際代碼。它包含一堆處理器必須執行的指令,如add eax, ebx(其中add是操作碼,而eax & ebx是參數)。該指令添加寄存器eaxebx的內容並將結果存儲在寄存器eax中。
  3. 數據段用於爲變量保留空間。例如在上面的程序中,我們需要爲整數a,bc保留空間。另外我們還需要爲字符串常量"%d"保留空間。因此保留的變量在內存中有一個固定的地址(鏈接和加載之後)。
  4. 除了所有這些,您還可以通過操作系統給予一點額外的空間。這被稱爲堆。你需要的任何額外內存都是從這個空間分配的。以這種方式分配的內存被稱爲動態內存。

讓我們看看動態內存的程序:

#include <stdio.h> 
#include <malloc.h> 

int main() { 
    int * a = malloc(3 * sizeof(int)); 

    a[0] = 3; 
    a[1] = 5; 
    a[2] = 7; 

    printf("a: %d\nb: %d\nc: %d\n", a[0], a[1], a[2]); 

    return 0; 
} 

因爲我們要分配內存動態,我們需要使用指針。這是因爲我們想要使用相同的變量來指向任意的內存位置(不一定每次都是相同的內存位置)。

所以我們創建了一個叫a的指針(int *)。 a的空間是從數據段中分配的(即它不是動態的)。然後我們調用malloc爲堆中的3個整數分配連續空間。第一個int的存儲器地址被返回並存儲在指針a中。

問:我們學到了什麼?

答:爲所有變量分配一個固定的空間量。每個變量都有一個固定的地址。我們也可以從堆中分配額外的內存,並將這個額外內存的地址存儲在一個指針中。這被稱爲動態存儲器方案。

從概念上講,這與我對變量爲便籤的解釋類似。所有變量(包括指針都是便條)。但是指針是特殊的,因爲它們引用一個內存位置(就像在JavaScript中引用對象一樣)。

然而,這是的相似之處。這裏有差異:

  1. 在C一切由值(包括在指針的地址)被通過。要通過傳遞參考您需要通過指針使用間接尋址。 JavaScript只通過價值傳遞原語。傳遞引用是由引擎透明地處理,就像傳遞任何其他變量一樣。
  2. 在C可以創建一個指向像int一個原始數據類型。在JavaScript中,您不能創建對像number這樣的原始值的引用。所有原語總是按值存儲。
  3. 在C中,您可以對指針執行各種操作。這被稱爲指針運算。 JavaScript沒有指針。它只有參考。因此你不能執行任何指針算術。

除了這三者之外,C和JavaScript最大的不同之處在於JavaScript中的所有變量實際上都是指針。由於JavaScript是一種動態語言,因此可以使用相同的變量在不同的時間點存儲numberstring

JavaScript是一種解釋語言,並且解釋器通常用C++編寫。因此,JavaScript中的所有變量都映射到宿主語言中的對象(甚至是基元)。

當我們在JavaScript中聲明一個變量時,解釋器會爲它創建一個新的泛型變量。然後當我們爲它賦值時(它是一個原語或引用),解釋器只是給它分配一個新的對象。它在內部知道哪些對象是原始的,哪些是實際的對象。

概念上,它就像做這樣的事情:

JSGenericObject ten = new JSNumber(10); // var ten = 10; 

問:這是什麼意思?

A:這意味着JavaScript中的所有值(基元和對象)都是從堆中分配的。即使變量本身也是從堆中分配的。聲明從堆棧分配原語並僅從堆分配對象是錯誤的。這是C和JavaScript之間最大的區別。

+1

嗨Aadit M Shah,你的回答非常清楚。儘管它的長度,它真的很有趣,並抓住我的注意。我從頭到尾都讀過它。但是......(可以這麼說),我需要像上面那樣對堆和棧進行解釋,而不是你花時間解釋的東西。我已經給你評分,但是......我不能接受它。你能否就堆和堆做一個例子,我肯定非常感謝。謝謝。 –

+0

@LacViet - 我更新了我的答案。希望有所幫助。 –

+1

aadit m shah,你的比喻是超級有用的,真的有助於更好地理解材料!感謝分享。 – wmock

0

原始值是在其語言實現的最低級別表示的數據,在JavaScript中是以下類型之一:數字,字符串,布爾值,未定義和空值。

1

一個基本類型的存儲器中存有固定大小。例如,一個數字佔用八個字節的內存,而一個布爾值只能用一個位表示。數字類型是原始類型中最大的。如果每個JavaScript變量都保留八個字節的內存,則該變量可以直接保存任何原始值。

這是一個過於簡單化,並不打算作爲實際JavaScript實現的描述。

引用類型是另一回事,但是。例如,對象可以具有任意長度 - 它們沒有固定的大小。數組也是如此:數組可以有任意數量的元素。同樣,一個函數可以包含任意數量的JavaScript代碼。由於這些類型沒有固定的大小,因此它們的值不能直接存儲在與每個變量相關聯的八個字節的內存中。相反,變量存儲對該值的引用。通常,這個引用是某種形式的指針或內存地址。它本身不是數據值,但它告訴變量在哪裏尋找值。

原始類型和引用類型之間的區別是重要的,因爲它們的行爲不同。考慮使用數字(原始類型)的以下代碼:

var a = 3.14; // Declare and initialize a variable 
var b = a;  // Copy the variable's value to a new variable 
a = 4;   // Modify the value of the original variable 
alert(b)  // Displays 3.14; the copy has not changed 

這段代碼沒有什麼值得驚訝的。現在考慮,如果我們稍微改變代碼,以便它使用數組(引用類型),而不是數字會發生什麼:

var a = [1,2,3]; // Initialize a variable to refer to an array 
var b = a;  // Copy that reference into a new variable 
a[0] = 99;  // Modify the array using the original reference 
alert(b);   // Display the changed array [99,2,3] using the new reference 

如果這個結果似乎並不令人意外給你,你已經非常熟悉的區別原始類型和參考類型之間。如果看起來令人驚訝,請仔細看看第二行。請注意,它是在此語句中分配的數組值的引用,而不是數組本身。在第二行代碼之後,我們仍然只有一個數組對象;我們碰巧有兩個引用它。

+0

感謝您的解釋,以及代碼片段!據我瞭解,對象和數組作爲引用類型背後的原因是它們可能(!)變得如此龐大,將它們存儲在堆棧上會是低效的,對吧? – Matt

+1

是的,你是對的!一般來說數組的大小很大。因此多次複製它們可能會佔用整個空間。 – noddy