【问题标题】:Understanding Javascript immutable variable理解 Javascript 不可变变量
【发布时间】:2013-04-20 01:13:05
【问题描述】:

我试图理解 Javascript 不可变变量的含义。如果可以的话:

var x = "astring";
x = "str";
console.log(x); //logs str` , then why it is immutable?

我能想到的唯一答案(从我知道的一点点 C 中)是 var x 是指向值为“astring”的内存块的指针,在第二条语句之后它指向另一个值为“海峡”。是这样吗?

还有一个额外的问题:我对 Javascript 的值类型感到困惑。引擎盖下的所有变量都是对象吗?偶数和字符串?

【问题讨论】:

  • 我看过那个帖子。但我不只是调用一个方法。我分配了一个新值。是一样的吗?
  • 分配一个新值就是这样:分配一个新值。您没有修改字符串,而是更改了引用。是的,变量引用对象:例如"hi there".split()typeof 5
  • 您的“额外问题”应该单独提出,假设尚未提出。

标签: javascript


【解决方案1】:

是不可变的; 变量不是;它们持有对其(原始)值的引用。

三种基本类型字符串、数字和布尔都有对应的类型,其实例是对象:字符串、数字、布尔
它们有时被称为 wrapper types

以下值为primitive

  • 字符串:“你好”
  • 数字:6、3.14(JavaScript 中的所有数字都是浮点数)
  • 布尔值:真、假
  • null:通常显式分配
  • 未定义:通常是默认(自动分配)值

所有其他值都是对象,包括原语的包装器。

所以:

  • 默认情况下对象是可变的
  • 对象具有唯一的身份,并通过引用进行比较
  • 变量保存对对象的引用
  • 原语是不可变的
  • 基元是按值比较的,它们没有个体身份

您可能会发现The Secret Life of JavaScript Primitives 是一个很好的解释。

另外,在 ES6 中有一个新的 const 关键字,它创建一个只读的命名常量,它不能通过赋值改变值或在脚本运行时重新声明。

希望这会有所帮助!

【讨论】:

  • 谢谢它的帮助。但我不知道什么是唯一身份。另外,我能找到更多关于原语如何与这些包装器相关联的分析信息吗?对我来说似乎很奇怪,我可以在原语上调用一个方法(例如 aString.length)。
  • wrapper 有方法length。请注意我的答案第二行中的大小写。这个link: The Secret Life of JavaScript Primitives 可能会有很大帮助!
  • 所以,如果我理解正确的话,那么与 C 相比,在 JavaScript 中,如果你有一个内存位置被引用,var x = 1;内容为 0000000000000001 然后 "immutability" 意味着我们不能将内存 lcoation 的内容更改为 00000000000000011。如果是这种情况,那么如果我写 var x=1; x=2;每次 JavaScript 都会为我提供一个新的内存位置并垃圾收集旧的无人看管的位置?
  • @pulp_fiction:是的,只要没有其他'var''references'那个'value'。但是,我想指出,在现实生活中,现代浏览器会进行各种优化,例如并非所有数字实际上都是浮动的,小整数是超快的(暗示它们内部也不是 32 位),观察和重新编译部分代码(取决于实际使用情况)等等。额外说明:但是,如果您有一个数组缓冲区,则可以覆盖该缓冲区内的特定地址,使它们更可预测(就 cpu 上的实际内存负载而言运行脚本)。
  • @ChrisAnderson:引用a(在您的示例中)指向一个对象(如果您愿意,a 的“值”是一个对象)。您没有 freeze 对象,因此您可以添加/删除/修改对象的属性/方法(“内容”,如果您愿意的话)甚至它的原型!因此,您的示例 (const a = {}; a.b = 1; a.b === 1) 完全符合它的预期。但是,如果您发布了const a = {}; a = 42;,那么应该会抛出Uncaught SyntaxError
【解决方案2】:

首先,在 C 中,“字符串是一个字符数组,最后一个 elem = '\0'”。它们是可变的。
如果你在 C 中这样声明和初始化一个字符串:

char str[] = "Foo";

您基本上在做的是保留 4 个字节(可能是 8 位字节,如果它伤害了您,请不要介意)。单词 str 用作指向该数组的第一个元素的指针。所以,如果你这样做:

str[0] or *(str) = 'G'

然后它将改变该地址的值而不是创建新数组。您可以通过打印 str 的地址来验证它。在这两种情况下都是一样的。

现在,如果 JavaScript 字符串是原始类型。对字符串的所有操作都是按值而不是按引用完成的。所以,这样做会产生 true。

var str1 = "foo";
var str2 = "foo";
str1 === str2; => true

字符串的初始化要求一个缓冲区以适应“foo”并将名称 str1 绑定到它。使它们不可变的原因是您无法更改该缓冲区。所以,你不能这样做:

str1[0] = 'G'

在非严格模式下执行该命令不会产生警告或错误,但不会改变str1。您可以通过

进行验证
console.log(str1) => "foo"

但如果你这样做:

str1 = "goo"

你实际上在做的是你要求一个新的缓冲区来适应“goo”并将标识符 str1 绑定到它。包含“foo”的旧缓冲区没有变化。

那么,“foo”会发生什么?

Java Script 有一个自动垃圾收集器。当它看到某个内存块不再可以被任何标识符或...引用时,它会认为该内存是空闲的。

数字,布尔值也是如此。 现在,关于包装对象! 每当您尝试像这样访问字符串上的属性时:

str1.length;

JavaScript 的作用是使用 String 类创建一个新对象并调用字符串上的方法。一旦函数调用返回,对象就会被销毁。 下面的代码进一步解释了它:

var str = "nature"; 
str.does = "nurtures"; //defining a new property; 
console.log(str.does) => undefined

因为对象已被销毁。 试试这个!

var str = new String("Nature");
str.does = "nurtures";
console.log(str) =>  ??

这个 str 确实是一个对象...

结论:在 C 中,在单个作用域中,变量名用作指针。所以,int、float、string 都是可变的。但在 Java Script 中,原始类型变量名用作值而不是引用

参考资料:C++ 入门加,Java Script The Definitive Guide,C by Stephen Kochan

【讨论】:

  • “对字符串的所有操作都是按值而不是按引用完成的”我很困惑。字符串真的不是对缓冲区的引用吗?
  • @LeCurious 在 C 中如果你喜欢这个 char str[] = "foo"。然后 str 将成为缓冲区的引用(指针)。您可以使用此参考对该缓冲区进行更改。但是,在 javaScript 中,如果你这样做 var str = "hi".那么 str 是一个只读标识符,用于访问存储 hi 的缓冲区。
  • @LeCurious 是的,你可以说在 javascript 中我们引用了只读缓冲区,而在 C 中我们引用了读写缓冲区。希望你开个玩笑:)
【解决方案3】:

你是对的。字符串(和数字)在 java 脚本(和许多其他语言)中是不可变的。变量是对它们的引用。当您“更改变量的值”时,您正在更改变量引用的字符串(或其他内容),而不是值本身。

【讨论】:

  • 我想我开始明白了,谢谢。您知道第一个值(在我的示例中为“astring”)会发生什么吗?它存在于内存中直到垃圾收集器通过?
【解决方案4】:

我认为许多新程序员认为不变性意味着原始值不能通过重新分配来改变。

var str = "testing";
var str = "testing,testing";
console.log(str); // testing, testing

var fruits = ["apple", "banana", "orange"];

fruits[0] = "mango";

console.log(fruits); //["mango", "banana", "orange"]

与可变和不可变类型关联的值可以通过重新分配来更改,如上面的字符串和数组示例所示。 但是,这些数据类型具有关联的函数(方法),用于操作属于每种数据类型的值。这是可以看到可变性/不变性的地方。由于数组是可变的,因此数组方法的任何操作都会直接影响数组。例如,

var fruits = ["mango","banana", "orange"];
fruits.pop(); 
console.log(fruits) //["mango", "banana"]


The array.pop() method deleted "orange" from the original fruits array.
But with strings for example,


var name = "Donald Trump";
name.replace("Donald", "President");
console.log(name)//Donald Trump

the original string remains intact!

不变性不允许通过 string 方法对原始字符串进行任何更改。相反,如果将方法操作分配给如下变量,则该方法会生成一个新字符串:

var name = "Donald Trump";
var newName = name.replace("Donald", "President");
console.log(newName);//President Trump

【讨论】:

  • 请注意,最后一个示例使用name 而不是var newName,但它的工作方式与分配新缓冲区的方式相同。旧缓冲区“Don...”一直存在,直到垃圾收集处理它。
【解决方案5】:

这里先了解一下,

let firstString = "Tap";

console.log(firstString);  //Output: Tap 

firstString[0] = "N";

console.log(firstString)   //Output: Tap

这就是我们可以看到不可变效应的地方!

【讨论】:

    【解决方案6】:

    这个定义中的不变性是历史性的。它与其他编程语言可以做的事情有关。

    我认为这是首先要理解的。对于只使用过 JavaScript 的程序员来说,这个问题可能看起来荒谬或不必要的迂腐。将基元描述为不可变就像将冰淇淋描述为无法跳跃一样。为什么我会认为它可以?只有与其他历史悠久的编程语言相比,在处理原始类型时才明显缺乏可变性。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-11-29
      • 1970-01-01
      • 2019-06-01
      • 1970-01-01
      • 2019-10-17
      • 1970-01-01
      • 1970-01-01
      • 2021-05-30
      相关资源
      最近更新 更多