原始值与参考值

IT技术 javascript
2021-01-13 02:33:42

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

var foo = 123;

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

6个回答

好的,假设您的变量是一张纸 - 一个便签。

注1:一个变量是一个便条

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

注意 2:您有无穷无尽的便签,其中存储了少量信息。

太好了,你可以在便签上写什么?我可以写:

  1. 是或否(布尔值)。
  2. 我的年龄(一个数字)。
  3. 我的名字(一个字符串)。
  4. 什么都没有(未定义)。
  5. 涂鸦或其他任何对我来说毫无意义的东西(null)。

所以我们可以在便利贴上写一些简单的东西(让我们居高临下地称它们为原始的东西)。

注3:您可以在便签上原始的东西。

因此,假设我30在便利贴上写字提醒自己为今晚我在我家举办的小聚会买 30 片奶酪(我的朋友很少)。

当我去把我的便利贴放在冰箱上时,我看到我妻子在冰箱上贴了另一个便利贴,上面也写着30(提醒我她的生日是这个月的 30 日)。

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

A:是的,他们都说30我们不知道是 30 片奶酪还是一个月的第 30 天,坦率地说,我们不在乎。对于一个不知道更好的人来说,一切都一样。

var slicesOfCheese = 30;
var wifesBirthdate = 30;

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

注释 4:两个写有相同内容的便签传达相同的信息,即使它们是两个不同的便签。

今晚我真的很兴奋——和老朋友一起出去玩,玩得很开心。然后我的一些朋友打电话给我,说他们将无法参加聚会。

所以我去我的冰箱,把30我的便利贴(不是我妻子的便利贴——那会让她很生气)上的那个擦掉,然后把它变成20.

注 5:您可以擦除便利贴上写的内容并写其他内容。

问:这一切都很好,但如果我的妻子想要写一份杂货清单,让我在我出去买奶酪的时候去拿,该怎么办?她是否需要为每件物品写一个便利贴?

A:不,她会拿一长串纸,在纸上写下食品杂货清单。然后她会写一张便条,告诉我在哪里可以找到杂货清单。

那么这里发生了什么?

  1. 杂货清单显然不是简单的(呃...原始)数据。
  2. 我妻子把它写在一张更长的纸上。
  3. 她在便利贴中写下了在哪里可以找到它。

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

回顾一下:

  1. 实际对象(杂货清单)在我的键盘下。
  2. 便利贴告诉我在哪里可以找到它(对象的地址)。

注 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 没有stackheap它是一种动态语言,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,b保留空间c此外,我们还需要为字符串常量 预留空间"%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;
}

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

因此,我们创建了一个名为int指针 ( int *) a的空间a是从数据段分配的(即它不是动态的)。然后我们调用malloc从堆中为 3 个整数分配连续空间。int返回第一个的内存地址并存储在指针中a

问:我们学到了什么?

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

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

然而,这就是相似之处。以下是差异:

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

除了这三个之外,C 和 JavaScript 的最大区别在于 JavaScript 中的所有变量实际上都是指针。因为 JavaScript 是一种动态语言,所以可以使用相同的变量在不同的时间点存储 anumber和 a string

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

当我们在 JavaScript 中声明一个变量时,解释器会为它创建一个新的通用变量。然后当我们为它分配一个值(无论是原始值还是引用)时,解释器简单地为它分配一个新对象。它在内部知道哪些对象是原始对象,哪些是实际对象。

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

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

问:这是什么意思?

A:这意味着 JavaScript 中的所有值(原语和对象)都是从堆中分配的。甚至变量本身也是从堆中分配的。说原语是从堆栈中分配的,而只有对象是从堆中分配的,这是错误的。这是 C 和 JavaScript 最大的区别。

这不是通过引用传递的意思。通过引用传递与传递引用不同;您可以按值或按引用传递引用。有关详细信息,请参阅此答案stackoverflow.com/a/6605700
2021-03-18 02:33:42
呵呵,喜欢比喻!读到最后,只想添加注释 8 :)。只是为了清楚表明您正在操作同一个对象:jsfiddle.net/DefML
2021-03-20 02:33:42
嗨 Aadit M Shah,您的回答非常明确。尽管它很长,但它确实很有趣并且抓住了我的注意力。我确实从头到尾读了一遍。但是......(可以这么说)我需要像上面那样对堆和堆栈进行解释,而不是你花时间解释的事情。我已经评价了你的答案,但......我不能接受。能否请您对堆和堆栈进行说明,我肯定会非常感激。谢谢你。
2021-03-23 02:33:42
哇哦,通常不喜欢用冗长的比喻来形容编程,但这是一个绝妙的解释
2021-03-25 02:33:42
aadit m shah,你的比喻非常有帮助,真的有助于更好地理解材料!感谢分享。
2021-04-03 02:33:42

Avariable可以包含两种值类型之一:primitive valuesreference values

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

基础:

对象是属性的聚合。属性可以引用 anobject或 a primitivePrimitives are values,它们没有属性。

更新:

JavaScript 有 6 种原始数据类型:StringNumberBooleanNullUndefinedSymbol(ES6 中的新内容)。除了 null 和 undefined 之外,所有原始值都具有环绕原始值的对象等效项,例如String对象环绕字符串原始值。所有原语都是不可变的。

@Talha - 在教授 JavaScript 等高级语言时,您真的需要解释动态内存分配等低级概念吗?
2021-03-17 02:33:42
是的。但是原始类型包括:string, number, boolean, null, undefined-String大写的“S”是string. 请参阅:MDN 词汇表 - 原始
2021-03-21 02:33:42
谢谢 Talha 你的回答真的很有帮助
2021-03-23 02:33:42
重要的区别在于,当一个引用值被传递给一个函数,并且该函数修改了它的内容时,调用者和任何其他引用该对象的函数都会看到这种变化。
2021-03-25 02:33:42
值是存放在栈上还是堆上不是由它们在 JS 中的类型决定的。它取决于存储它的变量的生命周期(受范围,尤其是闭包的影响,并取决于引擎的分析能力)。
2021-03-29 02:33:42

在 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 中的布尔值占用 4 个字节:stackoverflow.com/questions/4905861/...
2021-04-01 02:33:42

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

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

是的,你是对的!通常数组的大小很大。所以多次复制它们可能会占用整个空间。
2021-03-29 02:33:42
感谢您的解释,以及代码片段!据我了解,对象和数组成为引用类型的原因是它们可能(!)变得如此庞大,将它们存储在堆栈中会效率低下,对吗?
2021-03-31 02:33:42

正如在接受的答案和最高投票的答案中已经提到的,原始值是存储在堆栈中的数据,而引用值是存储在堆中的对象。

但这实际上意味着什么?它们在您的代码中的表现有何不同?

通过值访问原始值。因此,当您将具有原始值的变量 (var a) 分配给另一个变量 (var b) 时,变量 (a) 的值会被复制到新变量 (b) 中。而当你改变新变量(b)的值时,原变量的值保持不变。

当您将具有引用值的变量 (var x) 分配给另一个变量 (var y) 时,存储在变量 (x) 中的值也会复制到新变量 (y) 中。不同之处在于,现在存储在两个变量中的值都是存储在堆中的实际对象的引用。这意味着,x 和 y 都指向同一个对象。所以当你改变新变量(y)的值时,原来的value(x)的值也改变了(因为堆中的实际对象改变了)。