我读了一本名为“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和参考value的参考。
这只是一种精心设计的说法,即您将一个便利贴上写的所有内容复制到另一个便利贴上(复制原始值或引用无关紧要)。
复制引用时,被引用的对象不会移动(例如,我妻子的购物清单将始终保留在我的键盘下,但我可以将复制的便利贴带到任何我想要的地方——原始便利贴仍将在冰箱上)。
编辑 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;
}
因为我们要动态分配内存,所以需要使用指针。这是因为我们想要使用相同的变量来指向任意内存位置(不一定每次都相同的内存位置)。
因此,我们创建了一个名为的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
。基础:
对象是属性的聚合。属性可以引用 anobject
或 a 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
的存储器中存有固定大小。例如,一个数字占用八个字节的内存,而一个布尔值只能用一位来表示。
并且引用类型可以是任意长度——它们没有固定的大小。
原始类型在内存中具有固定大小。例如,一个数字占用八个字节的内存,而一个布尔值只能用一位来表示。数字类型是最大的原始类型。如果每个 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)的值也改变了(因为堆中的实际对象改变了)。