繁体   English   中英

原始值与参考值

[英]Primitive value vs Reference value

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

var foo = 123;

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

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

注 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:哦,最重要的事情是,当你传递变量身边你基本上传递的原始的价值参考价值的参考

这只是一种精心设计的说法,即您将一个便利贴上写的所有内容复制到另一个便利贴(无论您复制的是原始值还是引用都无关紧要)。

复制引用时,被引用的对象不会移动(例如,我妻子的购物清单将始终保留在我的键盘下,但我可以将复制的便利贴带到任何我想要的地方——原始便利贴仍将在冰箱上)。

编辑 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. 数据段用于为变量保留空间。 例如,在上面的程序中,我们需要为整数abc保留空间。 此外,我们还需要为字符串常量"%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;
}

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

所以我们创建了一个名为aint指针( 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 最大的区别。

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

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

基础知识:

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

更新:

JavaScript 有 6 种原始数据类型: StringNumberBooleanNullUndefinedSymbol (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在内存中具有固定大小。 例如,一个数字占用 8 个字节的内存,而一个布尔值只能用一位来表示。

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

原始类型在内存中具有固定大小。 例如,一个数字占用 8 个字节的内存,而一个布尔值只能用一位来表示。 数字类型是最大的原始类型。 如果每个 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)的值也改变了(因为堆中的实际对象改变了)。

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

原始与参考:

变量是 javascript 引擎为保存数据而保留的内存 (RAM) 段。 在这些变量中可以存储两种值:

  • 实际数据:例如字符串或数字。 这意味着实际内存(即内存中的字节)保存数据本身。
  • 对对象的引用:例如数组对象或函数对象。 这意味着这个变量只保存对这个对象所在的内存中另一个位置的引用

原始值和参考值的区别:

当一个原始值被分配给一个变量时,数据(位的值)就被复制了。 例如:

原始值:

 let num1 = 10; // What happens here is that the bits which are stored in the memory container // (ie variable num1) are literally copied into memory container num2 let num2 = num1;

参考值:

 let objRef1 = {prop1: 1} // we are storing the reference of the object which is stored in objRef1 // into objRef2. Now they are pointing to the same object let objRef2 = objRef1; // We are manipulating the object prop1 property via the refernce which // is stored in the variable objRef2 objRef2.prop1 = 2; // The object which objRef1 was pointing to was mutated in the previous action console.log(objRef1);

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM