为什么我可以在JavaScript中更改一个常量对象?

212

我知道ES6尚未标准化,但很多浏览器目前支持JS中的const关键字。

在规范中写道:

常量的值不能通过重新赋值进行更改,且常量不能被重新声明。因此,虽然可以声明一个未初始化的常量,但这样做是没有意义的。

当我做类似这样的事情时:

const xxx = 6;
xxx = 999;
xxx++;
const yyy = [];
yyy = 'string';
yyy = [15, 'a'];

我看到一切都没问题:`xxx`仍然是`6`,而`yyy`是`[]`。
但是如果我执行`yyy.push(6); yyy.push(1);`,我的常量数组已经被改变了。现在它是`[6, 1]`,顺便说一下,我还是不能用`yyy = 1;`来改变它。
这是一个bug吗,还是我漏掉了什么?我在最新的Chrome和FF29中尝试过。

1
你可以创建一个类,声明变量并在类内部为其分配值。然后,为该变量创建一个GETTER,并且不要实现SETTER。它应该实现常数。 - Andrew
18
谢谢您,但我并不是在问如何做到这一点。我只是好奇为什么 const 关键字会有这样的行为。 - Salvador Dali
13个回答

304
MDN documentation指出:

...常量不能通过重新赋值来改变
...常量不能重新声明

当你向数组或对象添加元素时,你并没有重新赋值或重新声明常量;它已经被声明和赋值。你只是在常量所指向的元素或属性列表中添加元素或属性。

所以这样是可以的:

const x = {};

x.foo = 'bar';

console.log(x); // {foo : 'bar'}

x.foo = 'bar2';

console.log(x); // {foo : 'bar2'}  

这个:
const y = [];

y.push('foo');

console.log(y); // ['foo']

y.unshift("foo2");

console.log(y); // ['foo2', 'foo']

y.pop();

console.log(y); // ['foo2']

但是这两者都不是。
const x = {};
x = {foo: 'bar'}; // error - re-assigning

const y = ['foo'];
const y = ['bar']; // error - re-declaring

const foo = 'bar'; 
foo = 'bar2';       // error - can not re-assign
var foo = 'bar3';   // error - already declared
function foo() {};  // error - already declared

19
所以你的意思是这不是一个错误,而是应该这样工作?因为我认为常量的理念就是它不能被改变。基本上程序员相信无论发生什么,常量内部的值都不会改变。 - Salvador Dali
2
我认为这并不容易,在这种情况下,常量的值是特定元素的数组。改变任何东西意味着你改变了 - veritas
9
是的,它应该按这种方式工作,您没有重新分配常量,它仍然是相同的引用,您只是向数组添加常量引用,而数组和对象就像“列表”一样,修改它们不会改变引用或重新声明常量。 - adeneo
52
@SalvadorDali: "constant"和 "read-only"是两个不同的概念。你的变量是"constant",但它指向的数组并不是"read-only"。 - Matt Burland
2
所以基本上变量是一个指针,而作为“const”,您不能更改它所引用或指向的内存地址,但您可以自由更改该内存地址所持有的值? - Nick Rolando
显示剩余2条评论

86
这是因为你的常量实际上存储了一个数组的引用。当你将某些内容添加到数组中时,你没有修改你的常量值,而是修改了它所指向的数组。如果你将一个对象分配给一个常量并尝试修改它的任何属性,则会发生同样的情况。
如果你想要冻结一个数组或对象以使其不能被修改,可以使用 Object.freeze 方法,它已经是 ECMAScript 5 的一部分。
const x = Object.freeze(['a'])
x.push('b')
console.log(x) // ["a"]

2
按照相同的逻辑,设定为5的常量five实际上并没有一个值为5,它只是对数字5的引用。因此,如果我执行five++,我并没有改变这个常量,只是改变了它所指向的数字。 - Anthony
4
@Anthony,引用仅适用于数组和对象,而不适用于原始值。 - Guilherme Sehn
6
在你的例子中,你改变了变量five所指向的数字(变量five原来是代表数字5的标签,现在它指向一个不同的数字:6)。在问题和答案的例子中,变量x始终指向相同的列表;如果x是常量,你无法使其指向另一个列表。唯一的区别是相同的列表可以增长或缩小;这只对数组和对象有可能发生,而原始数据类型不行。 - ShreevatsaR

21

在搜索为什么我可以更新一个已经定义为const的对象时,我看到了这篇文章。所以这里的重点是不是直接更新对象,而是更新它包含的属性。

例如,我的对象如下:

const number = {
    id:5,
    name:'Bob'
};

以上答案正确地指出了是对象是const而不是其属性。因此,我可以通过以下方式更新id或name:

number.name = 'John';

但是,我将无法像这样更新对象本身:

number = {
    id:5,
    name:'John'
  };

TypeError: Assignment to constant variable.

6
你的例子很实用,并且描述准确。 - Ebrahim

17

这与我所知道的每种编程语言的行为一致。

考虑C语言-数组只是指针的美化版。常量数组只意味着指针的值不会改变,但实际上该地址处包含的数据可以随意更改。

在Javascript中,您可以调用常量对象的方法(当然 - 否则常量对象就没有什么作用!)这些方法可能具有修改对象的副作用。由于Javascript中的数组也是对象,因此此行为也适用于它们。

您所确保的只是常量始终指向同一对象。对象本身的属性可以自由更改。


Swift是一种不像这样工作的语言。let someArray = ["one"]; someArray.append("two")无法编译,会出现error: cannot use mutating member on immutable value: 'someArray' is a 'let' constantnote: change 'let' to 'var' to make it mutable的错误提示。 - user137369
解释我为什么要踩这个问题。很明显,这个问题被提出的原因是与程序员通常使用的方式相反。在C/C++中,常量通常由不可变的宏定义,在PHP中则通过特定的define()函数定义。当程序员说“常量”时,他们通常指的是“不会改变的东西”,而不是底层的数学计算。这就是为什么只有在JavaScript中人们才会经常被这种奇怪的关键字使用所困惑的原因,因为这是一个糟糕的选择。这里的答案从技术上讲并不是不正确的,但也不是非常有用的。 - Teekin

10

关键字const有点误导性。

它并不定义一个常量值,而是定义了对一个值的常量引用。

由于这个原因,你不能:

  • 重新分配一个常量值
  • 重新分配一个常量数组
  • 重新分配一个常量对象

但是你可以:

  • 更改一个常量数组
  • 更改一个常量对象

1
谢谢您的留言。这篇文章阐述了JS中const的工作原理,相比于其他语言,特别是静态类型语言,它确实有些繁琐。 - Programmer Dancuk
点赞指出关键字误导了。 确实如此。 - Teekin

8
我认为这将更清楚地解释这个问题:https://codeburst.io/explaining-value-vs-reference-in-javascript-647a975e12a0
基本上,这意味着const始终指向内存中的同一地址。您可以更改存储在该地址中的值,但不能更改const指向的地址。
const指向保存原始值的地址时,您提到的const定义是正确的。这是因为您无法为此const分配一个值而不更改其地址(因为这就是分配原始值的方式),而且不允许更改const的地址。
如果const指向非原始值,则可以编辑地址的值。

这是另一个初学者友好的解释,关于原始类型和对象在内存中保存及其不同的行为方式(来自2017年,因此略有过时,但是非常好的主题介绍):https://www.youtube.com/watch?v=9ooYYRLdg_g - user1063287

4
const声明会创建一个只读的值引用。这并不意味着它所持有的值是不可变的,只是变量标识符不能被重新赋值。例如,在内容为对象的情况下,这意味着对象的内容(例如其参数)可以被改变。
另外,还有一个重要的注意事项:
全局常量不会成为window对象的属性...

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/const


1

常量的值不能通过重新赋值来更改,也不能重新声明。

const testData = { name:"Sandeep",lastName:"Mukherjee",company:"XYZ"}

第一种情况

testData = {name:"hello"}
console.log(testData);//throws an Error:Assignment to constant variable
Here we are reassigning testData again

第二种情况

const testData = {name:"Sandeep",lastName:"Mukherjee",company:"ABC"}
console.log(testData); //throws an Error: Identifier 'testData' has already been declared
Here we are redeclaring testData again

当使用const声明变量时,它意味着它指向某个内存位置。const的行为是我们可以操作存储在该内存位置中的值,但不能操作内存位置。当我们重新分配/重新声明const变量时,它不允许更改内存位置。

我们可以更改特定键的值

testData.company = "Google"
 console.log(testData);
 //{ name: 'Sandeep', lastName: 'Mukherjee', company: 'Google' }

我们可以向其添加任何新的键值对。
 testData.homeTown = "NewYork"
 console.log(testData)
 //{name: 'Sandeep',lastName:'Mukherjee',company:'Google',homeTown: 'NewYork'}

0

因为在const中,您可以更改对象的值,因此对象实际上并不存储分配数据,而是指向它。 因此,在JavaScript中,原始类型和对象之间存在差异。


0

const变量存储的是一个常量地址(例如0xFF2DFC),该地址是不变的。

常量并不是内存中的内容。

常量仅是内存地址

感谢您的阅读。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接