Answers:
这意味着一旦实例化对象,就无法更改其属性。在第一个警报中,您没有更改foo。您正在创建一个新字符串。这就是为什么在第二个警报中它将显示“ foo”而不是oo的原因。
这是否意味着在对字符串调用方法时,它将返回修改后的字符串,但不会更改初始字符串?
是。创建字符串后,任何操作都无法更改。现在,这并不意味着您不能将新的字符串对象分配给str
变量。您只是无法更改str引用的当前对象。
如果字符串是可变的,这是否意味着第二个alert()也将返回oo?
从技术上讲,没有,因为substring方法返回一个新字符串。使对象可变,不会改变方法。使其可变意味着从技术上讲,您可以使它成为子字符串,而不是创建新字符串,而是更改原始字符串。
在较低的级别上,不变性意味着将不会修改存储字符串的内存。创建字符串后"foo"
,将分配一些内存来存储值"foo"
。此内存不会被更改。例如,如果您substr(1)
使用新的字符串修改字符串,则将分配存储的另一部分内存"oo"
。现在您的内存中有两个字符串,"foo"
和"oo"
。即使您"foo"
不再使用它,它也会一直存在直到被垃圾回收。
字符串操作相对昂贵的原因之一。
我不确定JavaScript,但是在Java中,字符串通过“字符串常量池”向不可变性迈出了又一步。可以使用字符串文字("foo"
)或String
类构造函数来构造字符串。用字符串文字构造的字符串是字符串常量池的一部分,并且相同的字符串文字将始终是该池中的相同内存地址。
例:
String lit1 = "foo";
String lit2 = "foo";
String cons = new String("foo");
System.out.println(lit1 == lit2); // true
System.out.println(lit1 == cons); // false
System.out.println(lit1.equals(cons)); // true
在上面,两者lit1
和lit2
都是使用相同的字符串文字构造的,因此它们都指向相同的内存地址;lit1 == lit2
导致true
,因为它们是完全相同的对象。
但是,cons
是使用类构造函数构造的。尽管参数是相同的字符串常量,但构造函数会为分配新的内存cons
,这意味着cons
与lit1
和的对象不同lit2
,尽管包含相同的数据。
当然,由于三个字符串都包含相同的字符数据,因此使用equals
方法将返回true。
(当然,两种类型的字符串构造都是不可变的)
教科书中关于可变性的定义是有责任的,也可能会发生变化。在编程中,我们用这个词来表示其状态随时间变化的对象。一个不可变的值正好相反-创建后,就永远不会改变。
如果这看起来很奇怪,请允许我提醒您,我们一直使用的许多价值观实际上都是不可变的。
var statement = "I am an immutable value";
var otherStr = statement.slice(8, 17);
我认为没有人会惊讶地发现第二行绝不会改变语句中的字符串。实际上,没有字符串方法会更改它们所操作的字符串,它们都将返回新的字符串。原因是字符串是不可变的–它们不能更改,我们只能制造新的字符串。
字符串不是JavaScript内置的唯一不变值。数字也是不可变的。您甚至可以想象一个环境,其中计算表达式2 + 3会改变数字2的含义吗?听起来很荒谬,但是我们一直都在使用对象和数组来做到这一点。
从字符串到堆栈...摘自Eric Lippert的博客的简单易懂的示例:
像System.Collections.Generic.Stack这样的可变堆栈显然不适合。我们希望能够采用现有路径并为其最后一个元素的所有邻居从中创建新路径,但是将新节点压入标准堆栈会修改堆栈。我们必须先推入堆栈的副本,然后再推入堆栈,这很愚蠢,因为那样一来,我们将不必要地复制其所有内容。
不可变堆栈没有此问题。推入一个不变的堆栈只会创建一个全新的堆栈,该堆栈链接到旧堆栈的尾巴。由于堆栈是不可变的,因此不会有其他代码出现并弄乱尾部内容的危险。您可以继续使用旧堆栈来满足您的需求。
要深入了解不可变性,请阅读Eric从此开始的帖子: