原始属性被删除后,通过引用复制到第二个属性的对象仍然存在吗?

23

我原本认为对象会作为引用传递。但当我删除 b 后,它仍然存在于 c 中。请看这个例子:

第一部分对我来说很有意义,因为它是按引用传递的:

var a = {b: {val:true}};

a.c = a.b;
a.b.val = 'rawr';

console.log(uneval(a)); // outputs: "({b:{val:"rawr"}, c:{val:"rawr"}})"

现在这部分内容对我来说没有意义:

var a = {b: {val:true}};

a.c = a.b;
a.b.val = 'rawr';
delete a.b;

console.log(uneval(a)); // outputs: "({c:{val:"rawr"}})"

所以b属性被删除了,但是c属性仍然保存了在删除之前引用的属性。这是 JavaScript 中的一个 bug 吗?

编辑: 感谢所有的回复!所以这不是一个 bug,在实际应用中这种行为非常好,它允许人们更改“键”/“属性”名称,同时保留对象本身! :)


谢谢Awal!我会重新命名它。 - Noitidart
2
如果您正在使用调试器,最好只使用 console.log(a) 而不是 console.log(uneval(a)),因为它将显示属性和其他有用的调试信息(firebug 和 chrome console)。 - soktinpk
1
"我以为对象是按引用传递的。" JavaScript 始终是按值传递,就像 Java 一样。 - newacct
谢谢大家。关于 console.log,你们是对的,但我只是为了在这个例子中显示输出而使用了 uneval。感谢你们提供的传值注意事项。 - Noitidart
6个回答

55

我晚了吗?这是我的解释(也请参考其他答案,这只是为那些答案提供一个可视化的支持):

  1. 初始化一个空对象 a ,并将另一个对象 (value:true) 赋给它。

enter image description here

  1. 将空对象 a 赋值给属性 b ,引用到另一个对象 (value:true)。

enter image description here

  1. 将对象 a 赋值给属性 c ,这个属性指向相同的对象(value:true)。

enter image description here

  1. 删除 b,因此 a.b 不再指向子对象 (value:true)。

enter image description here

  1. 最终表示主对象 a

enter image description here

因此,我们可以通过可视化表示轻松地看到子对象是如何被保留的 :)


8
太棒了,我喜欢视觉呈现。 - Sacho
这真的很棒,感谢这个视觉解释!您能否在可视化中添加当我们删除b和c的两个属性时会发生什么。子对象应该消失,对吗? - Noitidart
@Noitidart 是的,它会消失。我会在有时间的时候添加解释。 - user3459110
2
几次简单的编辑后,我们用一些小图片描述了整个垃圾回收过程!(只需在云上写上GC-Magic,让对象消失即可) - Falco
当我试图用图形方式说明GC时,我通常会选择类似吃豆人的东西。 - Frost

32

不,这不是JavaScript中的一个错误。

a.c = a.b是在创建到同一对象的另一个链接,这意味着a.ba.c都引用同一个子对象{val: "rawr"}

当你执行delete a.b时,你并没有删除子对象,你只是从a中删除了a.b属性。这意味着a.c仍将引用相同的对象。

如果你想删除子对象,你还需要删除a.c属性。


3
谢谢你的解释,Frost!我的下一个问题是如何删除那个子对象,你也回答了,如果没有任何东西引用那个对象(通过删除 a.c),那么它就完全消失了。非常感谢!这种行为实际上非常好,它允许人们在保留对象的同时更改“键”/“属性”名称! :) - Noitidart

6

需要注意两点,第一,在其他答案中已经提到,delete 关键字仅从访问该属性的对象中删除属性。

第二点需要注意的是 JavaScript 从不按引用传递。它总是按值传递,有时候这个值是引用。

例如:

var a = {
   someObject: {}
};
var someObject = a.someObject;

someObject = 'Test';

console.log(a.someObject); // outputs {}

在传递引用的语言中,这可能会导致错误,因为它通常意味着强类型变量。

感谢您对“按值传递”进行的更正,这将对我非常有帮助。您提出的这一点非常好,我没有考虑到它而视为理所当然。真的非常感谢您的澄清。 - Noitidart

5

这是因为:

删除操作符会从对象中删除一个属性。 MDN Delete

对应于 Object {val: true}entry ba 中被移除。在 a 中,entry c 仍然参考该对象。如果您尝试删除 c.vala.b.val,您仍然可以看到效果会影响到另外一个。

您正在尝试做的事情,即释放数据并期望其被级联传递,在Javascript中不会发生。如果您具有C++背景,请考虑将所有Javascript对象视为引用计数。我可以删除指向此对象的引用(即“指向”该对象的entry),但是我无法删除对象本身。这是Javascript引擎的纯正权利。


谢谢Sharadh,这是一个非常有趣的观点:从a中删除val属性与b相同。 - Noitidart
当然,随时可以。这与您的示例相同。就像将对象中键val的值更改为rawr一样,在这里我们正在从对象本身中删除该键。 - Sharadh

5

使用这行代码,你认为c指向了b:

a.c = a.b;

实际上,C和B都指向{val:true}对象,因此如果您删除B,则该对象仍然存在。您可以简单地认为,C和B只是粘在{val:true}对象上的“标签”。


谢谢你,yelliver!你使用的不同词汇真正强化了其他人所说的内容。我非常感激这些多样的解决方案! - Noitidart

4

不,这不是一个bug。

delete 命令并没有删除属性所引用的对象,它仅仅只是删除了属性。如果对象在其他地方被引用(这种情况下是另一个属性),那么对象仍然存在。

这就好比你有两个指向同一对象的变量,然后改变其中一个变量的值:

var a = { val: true };
var b = a;
a = null;
console.log(b.val); // b still has a reference to the object

感谢Guffa提供了另一种例子来解释这个问题,我非常感激! - Noitidart

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