2012-11-07 175 views
21

我读了一本名为“Professional Javascript for web developer”的书,它说:“变量由参考值或原始值分配,参考值是存储在内存中的对象”。然后它没有说明如何存储Primitive值。所以我猜这不是存储在内存中。基于这一点,当我有这样的脚本:原始值vs参考值

var foo = 123; 

如何使用Javascript记住以后使用foo变量?

+5

听起来像垃圾书。当然'foo'存储在内存中。我想它试图说参考值是*对象*存储在内存中,但原始类型是*值*存储在内存中? – MadSkunk

+0

@LacViet - 请阅读我的答案。我相信它会更准确地回答您的问题:http://stackoverflow.com/a/13268731/783743 –

回答

41

A variable可以保存两种值类型之一:primitive valuesreference values

  • Primitive values是存储在堆栈上的数据。
  • Primitive value直接存储在变量访问的位置。
  • Reference values对象存储在。存储在变量位置中的
  • Reference value是指向存储对象的存储器中的位置的指针。
  • 原始类型包括UndefinedNullBooleanNumber,或String

基础:

对象是属性的聚合。酒店可以参考objectprimitivePrimitives are values,他们没有任何属性。

更新:

JavaScript有6元数据类型:布尔未定义符号(新中ES6)。除了空和未定义之外,所有基元值都具有包围基元值的对象等价物,例如, a 字符串对象环绕字符串基元。所有基元都是不可变的。

+7

是的。但是基本类型包括:'string,number,boolean,null,undefined' - 带有大写字母“S”的'String'是'string'的对象包装。请参阅:[MDN术语表 - 原语](https://developer.mozilla.org/en-US/docs/JavaScript/Glossary#primitive) – jfrej

+3

重要的区别在于,将参考值传递给函数时,函数修改它的内容,调用者和任何其他引用该对象的函数都会看到这种变化。 – Barmar

+0

@jfrej谢谢并更新 – Talha

2

在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在内存中具有固定大小。例如,一个数字占用八个字节的内存,而一个布尔值只能用一个位表示。

而引用类型可以是任意长度 - 它们没有固定的大小。

+0

JavaScript中的布尔值占用4个字节:https://stackoverflow.com/questions/4905861/memory-usage-of-different-data-types-in-javascript – Taurus

71

好吧,想象你的变量是一张纸 - 一个粘滞便笺。

注1:一个变量便条

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

注2:您有一个无尽供应的即时贴,其中存储少量信息

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

  1. 是或否(a 布尔值)。
  2. 我的年龄(a 号码)。
  3. 我的名字(a 字符串)。
  4. 完全没有(undefined)。
  5. 涂鸦或任何其他对我来说毫无意义的东西(null)。

因此,我们可以写简单的事情(让我们的是居高临下的,并呼吁他们原始事情)我们便签。

注意3:你可以在你的便签上写原始东西。

所以说我在便条上写30以提醒自己为我晚上投掷我的地方(我有很少朋友)的小派对买了30片奶酪。

当我将粘滞便笺放在冰箱上时,我看到我的妻子在冰箱上贴了另一个便条,上面写着30(提醒我说她的生日是在本月30号)。

问:这两个粘滞便笺都传达相同的信息?

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

var slicesOfCheese = 30; 
var wifesBirthdate = 30; 

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

注4:其中两个都写上他们同样的事情便签传达了相同的信息,即使它们是两个不同的便签。

今晚我真的很兴奋 - 和老朋友闲逛,玩得很开心。然后我的一些朋友打电话给我,说他们不能参加派对。

因此,我去我的冰箱,并在我的便条上清除30(不是我妻子的便条 - 这会让她非常生气),并使其成为20

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

问:这一切都很好,但如果我的妻子想写一份杂货清单让我在外出时买一些奶酪,该怎么办?她需要为每件物品写一张便条吗?

A:不,她会在纸上写一份较长的纸张清单并写上杂货清单。然后,她会写一张便条,告诉我在哪里可以找到杂货清单。

那么这里发生了什么?

  1. 杂货名单显然是不简单(呃... 原始)数据。
  2. 我的妻子在一张较长的纸上写下了它。
  3. 她写在哪里可以找到它的粘滞便笺。

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

要回顾一下:

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

注释6:参考值是对象的引用(它们将被找到的地址)。

问:我们怎么知道两个粘滞便笺说同一件事的时候?假如我的妻子再次购买食品杂货清单,以免我放错了第一张,并为它写了另一张便条。这两份清单都说了同样的事情,但粘滞便笺说同样的事情?

A:不是。第一个便签告诉我们在哪里可以找到第一个清单。第二个告诉我们在哪里可以找到第二个列表。这两个名单是否说同一件事并不重要。他们是两个不同的名单。

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没有一个堆栈。这是一种动态语言,JavaScript中的所有变量都是动态的。为了解释这一差别我把它比作C.

考虑下面的C程序:

#include <stdio.h> 

int main() { 
    int a = 10; 
    int b = 20; 
    int c = a + b; 
    printf("%d", c); 
    return 0; 
} 

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

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

让我们看看动态内存的程序:

#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 *)。 a的空间是从数据段中分配的(即它不是动态的)。然后我们调用malloc为堆中的3个整数分配连续空间。第一个int的存储器地址被返回并存储在指针a中。

问:我们学到了什么?

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

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

然而,这是的相似之处。这里有差异:

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

除了这三者之外,C和JavaScript最大的不同之处在于JavaScript中的所有变量实际上都是指针。由于JavaScript是一种动态语言,因此可以使用相同的变量在不同的时间点存储numberstring

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

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

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

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

问:这是什么意思?

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

+1

嗨Aadit M Shah,你的回答非常清楚。尽管它的长度,它真的很有趣,并抓住我的注意。我从头到尾都读过它。但是......(可以这么说),我需要像上面那样对堆和栈进行解释,而不是你花时间解释的东西。我已经给你评分,但是......我不能接受它。你能否就堆和堆做一个例子,我肯定非常感谢。谢谢。 –

+0

@LacViet - 我更新了我的答案。希望有所帮助。 –

+1

aadit m shah,你的比喻是超级有用的,真的有助于更好地理解材料!感谢分享。 – wmock

0

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

1

一个基本类型的存储器中存有固定大小。例如,一个数字占用八个字节的内存,而一个布尔值只能用一个位表示。数字类型是原始类型中最大的。如果每个JavaScript变量都保留八个字节的内存,则该变量可以直接保存任何原始值。

这是一个过于简单化,并不打算作为实际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 

如果这个结果似乎并不令人意外给你,你已经非常熟悉的区别原始类型和参考类型之间。如果看起来令人惊讶,请仔细看看第二行。请注意,它是在此语句中分配的数组值的引用,而不是数组本身。在第二行代码之后,我们仍然只有一个数组对象;我们碰巧有两个引用它。

+0

感谢您的解释,以及代码片段!据我了解,对象和数组作为引用类型背后的原因是它们可能(!)变得如此庞大,将它们存储在堆栈上会是低效的,对吧? – Matt

+1

是的,你是对的!一般来说数组的大小很大。因此多次复制它们可能会占用整个空间。 – noddy