我读了一本名为“Web 开发人员的专业 Javascript”的书,它说:“变量由参考值或原始值分配。参考值是存储在内存中的对象”。然后它没有说明原始值是如何存储的。所以我猜它没有存储在内存中。基于此,当我有这样的脚本时:
var foo = 123;
Javascript 如何记住foo
变量以供以后使用?
我读了一本名为“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:哦,重要的是,当您传递变量时,您实际上是通过 value 传递原始值,通过reference传递参考值。
这只是一种复杂的说法,即您正在将写在一个便签上的所有内容复制到另一个上(无论您是复制原始值还是引用都无关紧要)。
复制引用时,被引用的对象不会移动(例如,我妻子的购物清单将始终保留在我的键盘下,但我可以将复制的便签带到任何我想要的地方 - 原来的便签仍会在冰箱上)。
编辑 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;
}
当我们编译这个程序时,我们得到一个可执行文件。可执行文件分为多个段(或节)。这些段包括堆栈段、代码段、数据段、额外段等。
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;
}
因为我们想要动态分配内存,所以我们需要使用指针。这是因为我们想使用同一个变量来指向一个任意的内存位置(不一定每次都是同一个内存位置)。
所以我们创建了一个int
指针 ( int *
),称为a
. 的空间a
是从数据段分配的(即它不是动态的)。然后我们调用malloc
从堆中为 3 个整数分配连续空间。第一个的内存地址int
被返回并存储在指针中a
。
问:我们学到了什么?
答:为所有变量分配固定数量的空间。每个变量都有一个固定地址。我们还可以从堆中分配额外的内存,并将这些额外内存的地址存储在一个指针中。这称为动态内存方案。
从概念上讲,这类似于我解释的关于变量是便签的内容。所有变量(包括指针都是便签)。然而,指针是特殊的,因为它们引用一个内存位置(这就像在 JavaScript 中引用一个对象)。
然而,这就是相似之处的结束。以下是不同之处:
int
. 在 JavaScript 中,您不能创建对原始值的引用,例如number
. 所有原语总是按值存储。除了这三个之外,C 和 JavaScript 之间最大的区别在于 JavaScript 中的所有变量实际上都是指针。因为 JavaScript 是一种动态语言,所以可以使用相同的变量在不同的时间点存储 anumber
和 a 。string
JavaScript 是一种解释型语言,解释器通常是用 C++ 编写的。因此,JavaScript 中的所有变量都映射到宿主语言中的对象(甚至是原语)。
当我们在 JavaScript 中声明一个变量时,解释器会为它创建一个新的泛型变量。然后,当我们为它分配一个值(无论是原始值还是引用)时,解释器只需为其分配一个新对象。在内部,它知道哪些对象是原始对象,哪些是实际对象。
从概念上讲,它就像做这样的事情:
JSGenericObject ten = new JSNumber(10); // var ten = 10;
问:这是什么意思?
A:这意味着 JavaScript 中的所有值(基元和对象)都是从堆中分配的。甚至变量本身也是从堆中分配的。说原语是从堆栈中分配的,而只有对象是从堆中分配的,这是错误的。这是 C 和 JavaScript 最大的区别。
Avariable
可以保存以下两种值类型之一:primitive values
或reference values
。
Primitive values
是存储在堆栈上的数据。Primitive value
直接存储在变量访问的位置。Reference values
是存储在堆中的对象。Reference value
存储在变量 location 中的是指向内存中存储对象的位置的指针。Undefined
、Null
、Boolean
、Number
或String
。基础:
对象是属性的聚合。一个属性可以引用一个object
或一个primitive
。Primitives are values
,它们没有属性。
更新:
JavaScript 有 6 种原始数据类型:String、Number、Boolean、Null、Undefined、Symbol(ES6 中新增)。除 null 和 undefined 外,所有原始值都具有环绕原始值的对象等价物,例如,字符串对象环绕字符串原始值。所有原语都是不可变的。
在 javascriptPrimitive values
中,是存储在stack
.
Primitive value
直接存储在变量访问的位置。
Reference values
是存储在heap
. _
存储在变量 location 中的参考值是指向内存中存储对象的位置的指针。
JavaScript 支持五种原始数据类型:number, string, Boolean, undefined, and null
.
这些类型被称为原始类型,因为它们是可以构建更复杂类型的基本构建块。
在这五种中,只有number, string, and Boolean
实际存储数据意义上的真实数据类型。
Undefined and null
是在特殊情况下出现的类型。内存中的primitive type
大小是固定的。例如,一个数字占用 8 个字节的内存,一个布尔值可以只用一位来表示。
并且引用类型可以是任意长度——它们没有固定的大小。
原始类型在内存中具有固定大小。例如,一个数字占用 8 个字节的内存,一个布尔值可以只用一位来表示。数字类型是最大的原始类型。如果每个 JavaScript 变量保留 8 个字节的内存,则该变量可以直接保存任何原始值。
这是一种过度简化,并不打算作为对实际 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
如果这个结果对您来说并不奇怪,那么您已经非常熟悉原始类型和引用类型之间的区别了。如果它看起来确实令人惊讶,请仔细查看第二行。请注意,在此语句中分配的是对数组值的引用,而不是数组本身。在第二行代码之后,我们仍然只有一个数组对象;我们只是碰巧有两个引用它。
正如在接受的答案和投票最高的答案中已经提到的,原始值是存储在堆栈中的数据,参考值是存储在堆中的对象。
但这实际上意味着什么?它们在您的代码中的表现有何不同?
原始值按值访问。因此,当您将具有原始值的变量 (var a) 分配给另一个变量 (var b) 时,变量 (a) 的值将被复制到新变量 (b) 中。而当你改变新变量(b)的值时,原变量的值保持不变。
当您将具有引用值的变量 (var x) 分配给另一个变量 (var y) 时,存储在变量 (x) 中的值也会被复制到新变量 (y) 中。不同之处在于存储在两个变量中的值现在是存储在堆中的实际对象的引用。这意味着 x 和 y 都指向同一个对象。所以当你改变新变量(y)的值时,原来有价值的(x)的值也会改变(因为堆中的实际对象改变了)。
原始值是在语言实现的最低级别表示的数据,在 JavaScript 中是以下类型之一:数字、字符串、布尔值、未定义和空值。
变量是 javascript 引擎保留用于保存数据的内存段 (RAM)。在这些变量中可以存储 2 种值:
当一个原始值被分配给一个变量时,数据(位的值)刚刚被复制。例如:
原始值:
let num1 = 10;
// What happens here is that the bits which are stored in the memory container
// (i.e. 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);