Javascript指针/参考疯狂。有人可以解释一下吗?

时间:2011-11-29 22:02:52

标签: javascript pointers reference

Javascript通过引用传递对象。这很有道理。但是一旦你开始操纵这些物体,一切都会以一种看起来不直观的方式发挥作用。让我举一个例子:

var a, b;

a = {}
b = a;
a['one'] = {};

console.log( JSON.stringify(a) );
// outputs: {"one":{}}

console.log( JSON.stringify(b) );
// outputs: {"one":{}}

这一切都很好,因为现在b有一个指向a的指针,所以预计将内容分配到a也会影响b

但是如果我这样做的话:

a = a['one'];

console.log( JSON.stringify(a) );
// outputs: {}

console.log( JSON.stringify(b) );
// outputs: {"one":{}}

这对我来说很惊讶。我希望ab仍然相同(并{},因为a['one']之前设置为{}a设置为a['one'])。

但事实并非如此。 a似乎在b被分配给新内容时失去对b的引用,但a维持ab失去之前设置的值它引用a['two'] = 2; console.log( JSON.stringify(a) ); // outputs: {"two":2} console.log( JSON.stringify(b) ); // outputs: {"one":{"two":2}}

但是如果我这样做的话:

a

什么? b明显失去了对b的引用,但a似乎仍然引用了{}

空对象{{1}}是否指向内存中的某个位置,因此引用它的每个变量现在都指向同一个位置?

对此有把握的人能解释一下吗?

5 个答案:

答案 0 :(得分:201)

逐行跟踪您的示例:

a = {}

a现在引用了新对象。

b = a;

b现在引用a引用的同一对象。请注意,它不会引用a

a['one'] = {};

新对象现在有一个引用另一个新对象的索引'one'

当你这样做时

a = a['one'];

您正在设置a以引用a['one'],这是您在a['one'] = {}时创建的新对象。 b仍引用您使用a = {}创建的对象。

当您说'{34} a已失去对b"的引用时,您会混淆此问题因为a没有引用b,反之亦然。 ab引用对象,可以使它们引用其他对象。像这样:

使用a = {}; b = a,即可获得

a
 \
  \
   { }
  /
 /
b

然后使用a['one'] = {}获得

a
 \
  \
   { one: { } }
  /
 /
b

然后使用a = a['one']获得

a - - - - 
          \
   { one: { } }
  /
 /
b

答案 1 :(得分:45)

:P你正在深入细致的细节,我很高兴你问,因为到最后你会更聪明。

不要用指针来看待它,因为我认为这是你感到困惑的地方。可以考虑它而不是堆(或者只是“存储器”)和符号表。

让我们从代码的前几行开始:

var a, b;

a = {}
b = a;

您在此处所做的是在堆上创建一个对象,在符号表上创建两个符号。它看起来像这样:


符号表

+--------+-----------------+
| Symbol | Memory Location |
+--------+-----------------+
|      a |        0x400000 |
+--------+-----------------+
|      b |        0x400000 |
+--------+-----------------+

+----------+-----------------+
| Location | Value           |
+----------+-----------------+
| 0x400000 | <object val 1>  |
+----------+-----------------+


这里的事情变得有趣:对象有自己的“符号表”(通常这些只是哈希表,但称之为符号表可以使它更清晰)。

现在,在您的下一个语句之后,您需要考虑以下三点:全局符号表,<object val 1>的符号表和堆。

运行以下行:

a['one'] = {}

现在事情看起来像这样:


全球符号表

+--------+-----------------+
| Symbol | Memory Location |
+--------+-----------------+
|      a |        0x400000 |
+--------+-----------------+
|      b |        0x400000 |
+--------+-----------------+

<object val 1>的符号表

+--------+-----------------+
| Symbol | Memory Location |
+--------+-----------------+
|    one |        0x400004 |
+--------+-----------------+

+----------+-----------------+
| Location | Value           |
+----------+-----------------+
| 0x400000 | <object val 1>  |
+----------+-----------------+
| 0x400004 | <object val 2>  |     <---we created a new object on the heap
+----------+-----------------+


现在您运行了以下代码:

a = a['one'];

这应该是一个微不足道的改变。结果是:


全球符号表

+--------+-----------------+
| Symbol | Memory Location |
+--------+-----------------+
|      a |        0x400004 |
+--------+-----------------+
|      b |        0x400000 |
+--------+-----------------+

<object val 1>的符号表

+--------+-----------------+
| Symbol | Memory Location |
+--------+-----------------+
|    one |        0x400004 |
+--------+-----------------+

+----------+-----------------+
| Location | Value           |
+----------+-----------------+
| 0x400000 | <object val 1>  |
+----------+-----------------+
| 0x400004 | <object val 2>  | 
+----------+-----------------+


关于堆的内存位置应该有希望清楚地说明为什么你得到了输出。

现在事情变得更有趣了,因为现在你正在做:

a['two'] = 2;

好的,让我们一步一步来。

  • a指向包含0x400004
  • 的内存位置<object val 2>
  • <object val 2>是一个空对象,因此其符号表以空白
  • 开头
  • 通过运行此行,我们将变量“two”添加到<object val 2>的符号表中。

如果你还没有厌倦看这些图表,那么你将是。事情现在看起来像这样:


全球符号表

+--------+-----------------+
| Symbol | Memory Location |
+--------+-----------------+
|      a |        0x400004 |
+--------+-----------------+
|      b |        0x400000 |
+--------+-----------------+

<object val 1>的符号表

+--------+-----------------+
| Symbol | Memory Location |
+--------+-----------------+
|    one |        0x400004 |
+--------+-----------------+

<object val 2>的符号表

+--------+-----------------+
| Symbol | Memory Location |
+--------+-----------------+
|    two |        0x400008 |
+--------+-----------------+

+----------+-----------------+
| Location | Value           |
+----------+-----------------+
| 0x400000 | <object val 1>  |
+----------+-----------------+
| 0x400004 | <object val 2>  | 
+----------+-----------------+
| 0x400008 | 2 (literal val) |    <-- yes, even integers are stored on the heap
+----------+-----------------+        in JavaScript.


如果您努力花时间关注内存位置,您会看到浏览器显示正确的输出。

答案 2 :(得分:8)

将匿名对象视为具有名称:

a = {}; // The variable "a" now points to (holds) an anonymous object.
b = a; // "b" points to the same anonymous object held by "a".
a = 123; // "a" now holds some other value.
b; // "b" still holds the anonymous object.

关键是要记住变量包含对对象的引用,而不是对其他变量的引用。并且任何数量的变量都可以引用相同的对象。

答案 3 :(得分:6)

Javascript中的对象可以自己存在而无需名称。例如:

{}

是字典对象的新实例。

a = {};

创建一个新的字典对象,并使a引用它。现在

b = a;

使b引用相同的底层对象。然后,您可以将a指向其他位置:

a = "hi";

b仍然指向它之前执行的相同字典对象。 b的行为与您更改a指向的内容无关。

答案 4 :(得分:0)

据我所知,你已经过了 a ,所以我猜引擎将它保存在另一个内存空间中,而 b 仍然指向旧的 a 的内存地址(以某种方式不会被破坏)。