原始值与参考值
我读了一本名为“Web开发专业版Javascript”的书,它说:“variables由引用值或原始值赋值,引用值是存储在内存中的对象”。 然后它没有说明如何存储原始值。 所以我猜这不是存储在内存中。 基于此,当我有这样的脚本:
var foo = 123;
Javascript如何记住foo
variables以备后用?
variable
可以保存两种值types之一: primitive values
或reference values
。
-
Primitive values
是存储在堆栈上的数据。 -
Primitive value
直接存储在variables访问的位置。 -
Reference values
是存储在堆中的 对象 。 - 存储在variables位置中的
Reference value
是指向存储对象的存储器中的位置的指针。 - 原始types包括
Undefined
,Null
,Boolean
,Number
或String
。
基础:
对象是属性的集合。 一个属性可以引用一个object
或一个primitive
。 Primitives are values
,它们没有属性。
更新:
JavaScript有6种基本数据types: string , 数字 , 布尔值 , 空值 , 未定义 , 符号 (ES6中的新增内容)。 除了null和undefined之外,所有的primitives值都有对应的对象,它们包围了原始值,例如一个String对象环绕一个string原语。 所有的基元都是不变的。
好吧,想象你的variables是一张纸 – 一个便条。
注1: variables是粘滞便笺 。
现在,粘滞便笺非常小。 你只能写一点信息。 如果你想写更多的信息,你需要更多的便条,但这不是一个问题。 想象一下,你有无尽的粘滞便笺。
注2:您有无尽的便签,可存储less量信息。
太棒了,你可以在粘滞便笺上写什么? 我可以写:
- 是或否( 布尔值 )。
- 我的年龄(一个数字 )。
- 我的名字(一个string )
- 没有什么( 未定义 )。
- 一个涂鸦或任何其他什么对我来说完全没有意义( 空 )。
所以我们可以在我们的粘滞便笺上写简单的东西(让我们先屈服,称它们为原始的东西)。
注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杂货清单。
那么这里发生了什么?
- 杂货清单显然不是简单的 (erm … 原始 )数据。
- 我的妻子把它写在一张更长的纸上。
- 她写了一个粘滞便笺的地方。
亲爱的,食品杂货清单在你的键盘下。
回顾一下:
- 实际的对象(杂货清单)在我的键盘下。
- 粘滞便笺告诉我在哪里可以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; }
当我们编译这个程序,我们得到一个可执行文件。 可执行文件被分成多个段(或段)。 这些段包括堆栈段,代码段,数据段,额外段等。
- 当调用函数或中断处理程序时,堆栈段用于stored procedures的状态。 例如,当函数
f
调用函数g
,函数f
(当时所有寄存器中的值)的状态将被保存在一个堆栈中。 当g
返回到f
这些值被恢复。 - 代码段保存处理器要执行的实际代码。 它包含一堆处理器必须执行的指令,如
add eax, ebx
(其中add
是操作码,eax
&ebx
是参数)。 该指令添加寄存器eax
和ebx
的内容并将结果存储在寄存器eax
。 - 数据段用于为variables保留空间。 例如在上面的程序中,我们需要为整数
a
,b
和c
保留空间。 另外我们还需要为string常量"%d"
保留空间。 保留的variables在内存中有一个固定的地址(链接和加载后)。 - 除了所有这些,您还可以通过操作系统给予一点额外的空间。 这被称为堆。 你需要的任何额外的内存都是从这个空间分配的。 以这种方式分配的内存被称为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一个名为a
的int
指针( int *
)。 a的空间是从数据段中分配的(即不是dynamic的)。 然后我们调用malloc
从堆中为3个整数分配连续的空间。 第一个int
的内存地址被返回并存储在指针a
。
问:我们学到了什么?
答:为所有variables分配一个固定数量的空间。 每个variables都有一个固定的地址。 我们也可以从堆中分配额外的内存,并将这个额外内存的地址存储在一个指针中。 这被称为dynamic存储器scheme。
从概念上讲,这与我所说的有关variables是便签的解释类似。 所有variables(包括指针都是便条)。 但是指针是特殊的,因为它们引用一个内存位置(就像在JavaScript中引用一个对象一样)。
然而,这是相似之处的结束。 以下是区别:
- 在C中,所有东西都是按值传递的 (包括指针中的地址)。 要传递引用,您需要通过指针使用间接引用 。 JavaScript只是通过价值传递原语。 传递引用是由引擎透明地处理,就像传递任何其他variables一样。
- 在C中,可以创build一个指向像
int
这样的基本数据types的指针。 在JavaScript中,您不能创build对像number
这样的原始值的引用。 所有原语总是按值存储。 - 在C中,您可以对指针执行各种操作。 这被称为指针运算。 JavaScript没有指针。 它只有参考。 因此你不能执行任何指针算术。
除了这三个C和JavaScript之间最大的区别就是JavaScript中的所有variables实际上都是指针。 由于JavaScript是一种dynamic语言,因此可以使用相同的variables在不同的时间点存储number
和string
。
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之间的区别了。 如果确实看起来令人惊讶,那就仔细看看第二行。 请注意,它是在此语句中分配的数组值的引用,而不是数组本身。 在第二行代码之后,我们仍然只有一个数组对象; 我们碰巧有两个引用它。