原始值与参考值

我读了一本名为“Web开发专业版Javascript”的书,它说:“variables由引用值或原始值赋值,引用值是存储在内存中的对象”。 然后它没有说明如何存储原始值。 所以我猜这不是存储在内存中。 基于此,当我有这样的脚本:

var foo = 123; 

Javascript如何记住foovariables以备后用?

variable可以保存两种值types之一: primitive valuesreference values

  • Primitive values是存储在堆栈上的数据。
  • Primitive value直接存储在variables访问的位置。
  • Reference values是存储在堆中的 对象
  • 存储在variables位置中的Reference value是指向存储对象的存储器中的位置的指针。
  • 原始types包括UndefinedNullBooleanNumberString

基础:

对象是属性的集合。 一个属性可以引用一个object或一个primitivePrimitives are values ,它们没有属性。

更新:

JavaScript有6种基本数据types: string数字布尔值空值未定义符号 (ES6中的新增内容)。 除了null和undefined之外,所有的primitives值都有对应的对象,它们包围了原始值,例如一个String对象环绕一个string原语。 所有的基元都是不变的。

好吧,想象你的variables是一张纸 – 一个便条。

注1: variables粘滞便笺

现在,粘滞便笺非常小。 你只能写一点信息。 如果你想写更多的信息,你需要更多的便条,但这不是一个问题。 想象一下,你有无尽的粘滞便笺。

注2:您有无尽的便签,可存储less量信息。

太棒了,你可以在粘滞便笺上写什么? 我可以写:

  1. 是或否( 布尔值 )。
  2. 我的年龄(一个数字 )。
  3. 我的名字(一个string
  4. 没有什么( 未定义 )。
  5. 一个涂鸦或任何其他什么对我来说完全没有意义( )。

所以我们可以在我们的粘滞便笺上写简单的东西(让我们先屈服,称它们为原始的东西)。

注3:你可以在粘滞便笺上写下简单的东西。

所以说我在粘滞便笺上写了30个提醒自己为今晚我在我的地方扔的小派对买了30片奶酪(我有很less的朋友)。

当我把粘滞便笺放在冰箱上时,我发现我的妻子又在冰箱上贴了一张便条,里面还写着30 (提醒我说她的生日是在本月30号)。

问:这两个便签都传达了相同的信息吗?

答:是的,他们都说30 。 我们不知道这是30片奶酪还是本月的第30天,坦率地说我们不在乎。 对于一个不了解情况的人来说,都是一样的。

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

注4:两张粘在一起的便签上写着相同的东西,即使它们是两个不同的便签,也能传达相同的信息。

今晚我真的很兴奋 – 和老朋友一起玩,玩得很开心。 然后我的一些朋友打电话给我,说他们不能参加聚会。

所以我去我的冰箱,把粘在纸条上的30 (不是我妻子的便条 – 这会使她非常生气),把它做成20

注5:您可以清除粘滞便笺上写下的内容并写下其他内容。

问:这一切都很好,但是如果我的妻子想在我出去买一些奶酪的时候想写一份杂货清单,我该怎么办呢? 她需要为每个项目写一个便条吗?

A:不,她会拿一张较长的纸张清单,在那张纸上写杂货清单。 然后,她会写一个便条,告诉我在哪里可以find杂货清单。

那么这里发生了什么?

  1. 杂货清单显然不是简单的 (erm … 原始 )数据。
  2. 我的妻子把它写在一张更长的纸上。
  3. 她写了一个粘滞便笺的地方。

亲爱的,食品杂货清单在你的键盘下。

回顾一下:

  1. 实际的对象(杂货清单)在我的键盘下。
  2. 粘滞便笺告诉我在哪里可以find它(对象的地址)。

注6:引用值是对象的引用(它们将被find的地址)。

问:我们怎么知道两张便签说同一件事情? 假如我的妻子又买了一份购物清单,以防我错放了第一张,又为它写了一个便条。 这两份清单都是一样的,但是粘滞便笺也是这样说的吗?

答:不可以。第一个便条告诉我们在哪里可以find第一个清单。 第二个告诉我们在哪里find第二个列表。 这两个清单是否也是这样说并不重要。 他们是两个不同的名单。

 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中的基元引用 。 没有必要进入像和dynamic内存分配的东西。 如果您使用C / C ++编程,这一点很重要。

编辑1:哦,重要的是当你传递variables时,基本上是通过引用来传递基元值和引用值。

这只是一种复杂的方式,表示您将一个便签上的所有内容都复制到另一个便签上(不pipe您是复制原始值还是引用 )。

复制引用时,被引用的对象不会移动(例如,我妻子的购物清单将始终保留在我的键盘下方,但是我可以将粘贴的笔记复制到我想要的任何位置 – 原始便签仍然放在冰箱中)。

编辑2:回复@LacViet发表的评论:

对于初学者来说,我们正在谈论JavaScript,JavaScript没有堆栈 。 这是一种dynamic语言,JavaScript中的所有variables都是dynamic的。 为了解释这个差别,我会把它和C.

考虑下面的C程序:

 #include <stdio.h> int main() { int a = 10; int b = 20; int c = a + b; printf("%d", c); return 0; } 

当我们编译这个程序,我们得到一个可执行文件。 可执行文件被分成多个段(或段)。 这些段包括堆栈段,代码段,数据段,额外段等。

  1. 当调用函数或中断处理程序时,堆栈段用于stored procedures的状态。 例如,当函数f调用函数g ,函数f (当时所有寄存器中的值)的状态将被保存在一个堆栈中。 当g返回到f这些值被恢复。
  2. 代码段保存处理器要执行的实际代码。 它包含一堆处理器必须执行的指令,如add eax, ebx (其中add是操作码, eaxebx是参数)。 该指令添加寄存器eaxebx的内容并将结果存储在寄存器eax
  3. 数据段用于为variables保留空间。 例如在上面的程序中,我们需要为整数abc保留空间。 另外我们还需要为string常量"%d"保留空间。 保留的variables在内存中有一个固定的地址(链接和加载后)。
  4. 除了所有这些,您还可以通过操作系统给予一点额外的空间。 这被称为堆。 你需要的任何额外的内存都是从这个空间分配的。 以这种方式分配的内存被称为dynamic内存。

我们来看一个带有dynamic内存的程序:

 #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; } 

因为我们要dynamic地分配内存,我们需要使用指针。 这是因为我们想要使用相同的variables来指向任意的内存位置(不一定每次都是相同的内存位置)。

所以我们创build一个名为aint指针( int * )。 a的空间是从数据段中分配的(即不是dynamic的)。 然后我们调用malloc从堆中为3个整数分配连续的空间。 第一个int的内存地址被返回并存储在指针a

问:我们学到了什么?

答:为所有variables分配一个固定数量的空间。 每个variables都有一个固定的地址。 我们也可以从堆中分配额外的内存,并将这个额外内存的地址存储在一个指针中。 这被称为dynamic存储器scheme。

从概念上讲,这与我所说的有关variables是便签的解释类似。 所有variables(包括指针都是便条)。 但是指针是特殊的,因为它们引用一个内存位置(就像在JavaScript中引用一个对象一样)。

然而,这是相似之处的结束。 以下是区别:

  1. 在C中,所有东西都是按值传递的 (包括指针中的地址)。 要传递引用,您需要通过指针使用间接引用 。 JavaScript只是通过价值传递原语。 传递引用是由引擎透明地处理,就像传递任何其他variables一样。
  2. 在C中,可以创build一个指向像int这样的基本数据types的指针。 在JavaScript中,您不能创build对像number这样的原始值的引用。 所有原语总是按值存储。
  3. 在C中,您可以对指针执行各种操作。 这被称为指针运算。 JavaScript没有指针。 它只有参考。 因此你不能执行任何指针算术。

除了这三个C和JavaScript之间最大的区别就是JavaScript中的所有variables实际上都是指针。 由于JavaScript是一种dynamic语言,因此可以使用相同的variables在不同的时间点存储numberstring

JavaScript是一种解释型语言,解释器通常用C ++编写。 因此,JavaScript中的所有variables都被映射到宿主语言的对象(甚至是基元)。

当我们在JavaScript中声明一个variables时,解释器为它创build一个新的genericsvariables。 然后,当我们为它赋值时(它是一个原语或引用),解释器只是给它分配一个新的对象。 它在内部知道哪些对象是原始的,哪些是实际的对象。

从概念上讲,就像做这样的事情:

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

问:这是什么意思?

答:这意味着JavaScript中所有的值(原语和对象)都是从堆中分配的。 即使variables本身也是从堆中分配的。 声明从堆栈分配原语并仅从堆分配对象是错误的。 这是C和JavaScript最大的区别。

在JavaScript中, Primitive values是存储在stack中的数据

Primitive value直接存储在variables访问的位置。

Reference values是存储在heap 对象

存储在variables位置中的参考值是指向存储对象的存储器中的位置的指针。

JavaScript支持五种基本数据types: number, string, Boolean, undefined, and null

这些types被称为基本types,因为它们是可以构build更复杂types的基本构build块。

在这五个实际存储数据的意义上,只有number, string, and Boolean是真实的数据types。

Undefined and null是在特殊情况下产生的types。 primitive type在内存中具有固定大小。 例如,一个数字占用八个字节的内存,而一个布尔值只能用一个位表示。

引用types可以是任意长度的 – 它们没有固定的大小。

原始值是在其语言实现的最低级别表示的数据,在JavaScript中是以下types之一:数字,string,布尔值,未定义和空值。

原始types在内存中具有固定大小。 例如,一个数字占用八个字节的内存,而一个布尔值只能用一个位表示。 数字types是原始types中最大的。 如果每个JavaScriptvariables保留八个字节的内存,variables可以直接保存任何原始值。

这是一个过于简单化,并不是为了描述实际的JavaScript实现。

然而,引用types是另一回事。 对象,例如,可以是任何长度 – 他们没有一个固定的大小。 数组也是如此:一个数组可以有任意数量的元素。 同样,一个函数可以包含任何数量的JavaScript代码。 由于这些types不具有固定的大小,因此它们的值不能直接存储在与每个variables相关联的八个字节的存储器中。 相反,variables存储对该值的引用。 通常,这个引用是某种forms的指针或内存地址。 这不是数据值本身,而是告诉variables在哪里查找值。

原始types和引用types之间的区别是重要的,因为它们的行为不同。 考虑下面使用数字(原始types)的代码:

 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 

这个代码没有什么奇怪的。 现在考虑如果我们稍微改变一下代码,以便使用数组(一个引用types)而不是数字:

 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 

如果这个结果对你来说看起来并不奇怪,那么你已经很熟悉原始types和引用types之间的区别了。 如果确实看起来令人惊讶,那就仔细看看第二行。 请注意,它是在此语句中分配的数组值的引用,而不是数组本身。 在第二行代码之后,我们仍然只有一个数组对象; 我们碰巧有两个引用它。