[英]Primitive value vs Reference value
我讀了一本名為“Web 開發人員的專業 Javascript”的書,它說:“變量由引用值或原始值分配。引用值是存儲在內存中的對象”。 然后它沒有說明原始值是如何存儲的。 所以我想它沒有存儲在內存中。 基於此,當我有這樣的腳本時:
var foo = 123;
Javascript 如何記住foo
變量以備后用?
好的,假設您的變量是一張紙 - 一個便簽。
注 1:變量是便簽。
現在,便簽非常小。 你只能在上面寫一點信息。 如果你想寫更多的信息,你需要更多的便簽,但這不是問題。 想象一下,您有無窮無盡的便利貼。
注意 2:您有無窮無盡的便簽,其中存儲了少量信息。
太好了,你可以在便簽上寫什么? 我可以寫:
所以我們可以在便利貼上寫一些簡單的東西(讓我們居高臨下地稱它們為原始的東西)。
注3:您可以在便簽上寫原始的東西。
因此,假設我在便利貼上寫了30
,以提醒自己為今晚我在我家舉辦的小聚會買 30 片奶酪(我的朋友很少)。
當我去把我的便利貼貼在冰箱上時,我看到我妻子在冰箱上貼了另一張便利貼,上面也寫着30
(提醒我她的生日是這個月的 30 號)。
問:兩個便簽都傳達了相同的信息嗎?
A:是的,他們都說30
。 我們不知道是 30 片奶酪還是一個月的第 30 天,坦率地說,我們不在乎。 對於一個不知道更好的人來說,一切都一樣。
var slicesOfCheese = 30;
var wifesBirthdate = 30;
alert(slicesOfCheese === wifesBirthdate); // true
注釋 4:兩個寫有相同內容的便簽傳達相同的信息,即使它們是兩個不同的便簽。
今晚我真的很興奮——和老朋友一起出去玩,玩得很開心。 然后我的一些朋友打電話給我,說他們將無法參加聚會。
所以我去我的冰箱,把我便利貼上的30
擦掉(不是我妻子的便利貼——那會讓她很生氣)並把它變成20
。
注 5:您可以擦除便利貼上寫的內容並寫其他內容。
問:這一切都很好,但如果我的妻子想要寫一份雜貨清單,讓我在出去買奶酪的時候去拿,該怎么辦? 她是否需要為每件物品寫一個便利貼?
A:不,她會拿一長串紙,然后在紙上寫下食品清單。 然后她會寫一張便條,告訴我在哪里可以找到雜貨清單。
那么這里發生了什么?
親愛的,雜貨清單在你的鍵盤下面。
回顧一下:
注 6:引用值是對對象(將被找到的地址)的引用。
問:我們怎么知道兩個便簽說的是同一件事? 假設我的妻子制作了另一個購物清單,以防我放錯了第一個,並為它寫了另一個便利貼。 兩個列表都說同樣的話,但便利貼是否說同樣的話?
答:不可以。第一個便簽告訴我們在哪里可以找到第一個列表。 第二個告訴我們在哪里可以找到第二個列表。 這兩個列表是否說相同的事情並不重要。 它們是兩個不同的列表。
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 沒有stack或heap 。 它是一種動態語言,JavaScript 中的所有變量都是動態的。 為了解釋差異,我將它與 C 進行比較。
考慮以下 C 程序:
#include <stdio.h>
int main() {
int a = 10;
int b = 20;
int c = a + b;
printf("%d", c);
return 0;
}
當我們編譯這個程序時,我們會得到一個可執行文件。 可執行文件被分成多個段(或節)。 這些段包括堆棧段、代碼段、數據段、額外段等。
f
調用函數g
然后功能的狀態f
(在此時的寄存器的所有值)被保存在堆棧中。 當g
將控制權返回給f
這些值就會恢復。add eax, ebx
(其中add
是操作碼, eax
& ebx
是參數)。 該指令將寄存器eax
和ebx
的內容相加,並將結果存儲在寄存器eax
。a
、 b
和c
保留空間。 此外,我們還需要為字符串常量"%d"
保留空間。 保留的變量因此在內存中具有固定地址(在鏈接和加載之后)。讓我們看一個具有動態內存的程序:
#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
指針( int *
)。 對於空間a
從數據段中被分配(即它不是動態的)。 然后我們調用malloc
為堆中的 3 個整數分配連續空間。 返回第一個int
的內存地址並存儲在指針a
。
問:我們學到了什么?
答:為所有變量分配了固定數量的空間。 每個變量都有一個固定地址。 我們也可以從堆中分配額外的內存,並將這個額外內存的地址存儲在一個指針中。 這稱為動態內存方案。
從概念上講,這類似於我解釋的變量是便簽。 所有變量(包括指針都是便簽)。 然而,指針是特殊的,因為它們引用一個內存位置(這就像在 JavaScript 中引用一個對象)。
然而,這就是相似之處。 以下是差異:
int
的指針。 在 JavaScript 中,您不能創建對像number
這樣的原始值的引用。 所有原語總是按值存儲。 除了這三個之外,C 和 JavaScript 的最大區別在於 JavaScript 中的所有變量實際上都是指針。 因為 JavaScript 是一種動態語言,所以可以使用相同的變量在不同的時間點存儲number
和string
。
JavaScript 是一種解釋型語言,解釋器通常是用 C++ 編寫的。 因此,JavaScript 中的所有變量都映射到宿主語言中的對象(甚至是原語)。
當我們在 JavaScript 中聲明一個變量時,解釋器會為它創建一個新的通用變量。 然后,當我們為其分配一個值(無論是原始值還是引用)時,解釋器只是為其分配一個新對象。 它在內部知道哪些對象是原始對象,哪些是實際對象。
從概念上講,這就像做這樣的事情:
JSGenericObject ten = new JSNumber(10); // var ten = 10;
問:這是什么意思?
A:這意味着 JavaScript 中的所有值(原語和對象)都是從堆中分配的。 甚至變量本身也是從堆中分配的。 說原語是從堆棧中分配的,而只有對象是從堆中分配的,這是錯誤的。 這是 C 和 JavaScript 最大的區別。
variable
可以保存兩種值類型之一: primitive values
或reference values
。
Primitive values
是存儲在堆棧上的數據。Primitive value
直接存儲在變量訪問的位置。Reference values
是存儲在堆中的對象。Reference value
是指向存儲對象的內存位置的指針。Undefined
、 Null
、 Boolean
、 Number
或String
。基礎知識:
對象是屬性的聚合。 一個屬性可以引用一個object
或一個primitive
。 Primitives are values
,它們沒有屬性。
更新:
JavaScript 有 6 種原始數據類型: String 、 Number 、 Boolean 、 Null 、 Undefined 、 Symbol (ES6 中的新內容)。 除了 null 和 undefined 之外,所有原始值都具有環繞原始值的對象等效項,例如String對象環繞字符串原始值。 所有原語都是不可變的。
在 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
在內存中具有固定大小。 例如,一個數字占用 8 個字節的內存,而一個布爾值只能用一位來表示。
並且引用類型可以是任意長度——它們沒有固定的大小。
原始類型在內存中具有固定大小。 例如,一個數字占用 8 個字節的內存,而一個布爾值只能用一位來表示。 數字類型是最大的原始類型。 如果每個 JavaScript 變量保留 8 個字節的內存,則該變量可以直接保存任何原始值。
這是一種過度簡化,並不打算作為實際 JavaScript 實現的描述。
然而,引用類型是另一回事。 例如,對象可以是任意長度——它們沒有固定的大小。 數組也是如此:一個數組可以有任意數量的元素。 同樣,一個函數可以包含任意數量的 JavaScript 代碼。 由於這些類型沒有固定的大小,它們的值不能直接存儲在與每個變量關聯的 8 個字節的內存中。 相反,該變量存儲對該值的引用。 通常,此引用是某種形式的指針或內存地址。 它不是數據值本身,而是告訴變量去哪里尋找值。
原始類型和引用類型之間的區別很重要,因為它們的行為不同。 考慮以下使用數字(原始類型)的代碼:
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
如果您對這個結果並不感到驚訝,那么您已經非常熟悉原始類型和引用類型之間的區別。 如果確實令人驚訝,請仔細查看第二行。 請注意,在此語句中分配的是對數組值的引用,而不是數組本身。 在第二行代碼之后,我們仍然只有一個數組對象; 我們只是碰巧有兩個引用它。
正如在接受的答案和最高投票的答案中已經提到的,原始值是存儲在堆棧中的數據,而引用值是存儲在堆中的對象。
但這實際上意味着什么? 它們在您的代碼中的表現有何不同?
通過值訪問原始值。 因此,當您將具有原始值的變量 (var a) 分配給另一個變量 (var b) 時,變量 (a) 的值會被復制到新變量 (b) 中。 而當你改變新變量(b)的值時,原變量的值保持不變。
當您將具有引用值的變量 (var x) 分配給另一個變量 (var y) 時,存儲在變量 (x) 中的值也會復制到新變量 (y) 中。 不同之處在於,現在存儲在兩個變量中的值都是存儲在堆中的實際對象的引用。 這意味着,x 和 y 都指向同一個對象。 所以當你改變新變量(y)的值時,原來的value(x)的值也改變了(因為堆中的實際對象改變了)。
原始值是在語言實現的最低級別表示的數據,在 JavaScript 中是以下類型之一:數字、字符串、布爾值、未定義和空值。
變量是 javascript 引擎為保存數據而保留的內存 (RAM) 段。 在這些變量中可以存儲兩種值:
當一個原始值被分配給一個變量時,數據(位的值)就被復制了。 例如:
原始值:
let num1 = 10; // What happens here is that the bits which are stored in the memory container // (ie variable num1) are literally copied into memory container num2 let num2 = num1;
參考值:
let objRef1 = {prop1: 1} // we are storing the reference of the object which is stored in objRef1 // into objRef2. Now they are pointing to the same object let objRef2 = objRef1; // We are manipulating the object prop1 property via the refernce which // is stored in the variable objRef2 objRef2.prop1 = 2; // The object which objRef1 was pointing to was mutated in the previous action console.log(objRef1);
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.