【问题标题】:Javascript pointer/reference craziness. Can someone explain this?Javascript 指针/引用疯狂。有人可以解释一下吗?
【发布时间】:2012-01-09 05:47:09
【问题描述】:

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 保持aa 失去对b 的引用之前设置的值。

但是如果我这样做:

a['two'] = 2;

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

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

什么? a 显然失去了对b 的引用,但b 似乎仍然对a 有一些引用。

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

对此有深入了解的人可以给我解释一下吗?

【问题讨论】:

  • “现在b 有一个指向a 的指针”是谎言开始的地方。 “现在b 指向与a 相同的位置”是解决方法。
  • "Javascript 通过引用传递对象" - 不,它没有。它按值传递,但在对象的情况下,传递的值是对对象的引用,而不是对象本身的副本。这是一个重要的区别。如果您不是在谈论函数参数,那么谈论“传递”值或引用是没有意义的,但无论如何:如果它通过引用传递,那么您可以更改原始变量 a 指向的对象更改第二个变量 b 指向的内容 - 你绝对不能这样做(无论是否在函数中)。
  • @nnnnnn 我现在很困惑。如果您假设 Javascript 通过引用完成所有操作,那么这如何导致能够通过更改 b 来更改 a 指向的对象?似乎ab 是相互独立的引用,就像指针一样,而且似乎Javascript 仅通过引用传递,它只是不传递对变量的引用,它传递对变量所引用的引用。
  • @Seth Carnegie:"Pass by reference" 是一个标准的非语言特定的编程术语,许多语言都支持它而不向程序员公开指针,但 JavaScript 不这样做。您所说的是正确的:“按值传递引用”是 JavaScript 对象所发生的事情。我很抱歉我之前没能这么说。我想我们一直在绕圈子,因为术语,抱歉。

标签: javascript pointers reference


【解决方案1】:

逐行跟随您的示例:

a = {}

a 现在引用新对象。

b = a;

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

a['one'] = {};

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

当你这样做时

a = a['one'];

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

当您说“a 已失去对 b 的引用”时,您会混淆这个问题,因为 a 不引用 b ,反之亦然。 ab 引用对象,也可以使它们引用其他对象。像这样:

使用a = {}; b = a,您会得到

a
 \
  \
   { }
  /
 /
b

然后用a['one'] = {} 你得到

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

然后用a = a['one'] 你得到

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

【讨论】:

  • 有时 a = {} 的简单性会失去指针的清晰度。如果有帮助,当您在脑海中阅读 {} 时,将其替换为“new object()”并记住它正在创建一个新引用。
  • Awww...史诗般的失败。我发布了(我认为)是解释它的好方法,然后我看到了这个可视化...*叹息*...+1。
【解决方案2】:

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

不要从指针的角度来看待它,因为我认为这就是你感到困惑的地方。考虑一下堆(或者如果你愿意,也可以只是“内存”)和符号表。

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

var a, b;

a = {}
b = a;

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


符号表

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

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

.


这里是有趣的地方:对象有自己的“符号表”(通常这些只是哈希表,但称它为符号表可以更清楚)。

现在,在您的下一条语句之后,您需要考虑 3 件事:全局符号表、&lt;object val 1&gt; 的符号表和堆。

运行以下行:

a['one'] = {}

现在事情看起来像这样:


全局符号表

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

&lt;object val 1&gt; 的符号表

+--------+-----------------+
| 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 |
+--------+-----------------+

&lt;object val 1&gt;的符号表

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

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

.


按照堆的内存位置应该可以清楚地说明为什么你会得到你所做的输出。

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

a['two'] = 2;

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

  • a 指向内存位置0x400004,其中包含&lt;object val 2&gt;
  • &lt;object val 2&gt; 是一个空对象,因此它的符号表一开始是空的
  • 通过运行这一行,我们将变量“two”添加到&lt;object val 2&gt; 的符号表中。

如果您还没有厌倦查看这些图表,那么您一定会厌倦的。事情现在看起来像这样:


全局符号表

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

&lt;object val 1&gt;的符号表

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

&lt;object val 2&gt; 的符号表

+--------+-----------------+
| 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.

.


如果您认真花时间跟踪内存位置,您将看到您的浏览器显示了正确的输出。

【讨论】:

  • 哈,你的图表比我的详细得多(我只是做带箭头的图表)。 +1
  • @SethCarnegie,是的,但是您可以在更小的空间内获得相同的观点。
  • 是否有任何工具可以显示 JavaScript 可变内存位置?或者,是否有任何积极的方式来修补/调试内存?
  • @P.Brian.Mackey...我不知道。这是一种很好的思考方式,但是现代 JavaScript 引擎优化到了极致,所以你不太可能有一个工具给你这样的图片。 (例如:符号表被废弃以支持偏移,并且使用隐藏的类结构,对象的实际结构可能要复杂得多,除此之外......它是一个长长的兔子洞......坚持心智模型现在:P)
【解决方案3】:

把匿名对象想象成自己有一个名字:

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.

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

【讨论】:

    【解决方案4】:

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

    {}
    

    是一个字典对象的新实例。

    a = {};
    

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

    b = a;
    

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

    a = "hi";
    

    b 仍然指向它之前所做的同一个字典对象。 b 的行为与您如何更改 a 指向的内容无关。

    【讨论】:

      【解决方案5】:

      据我所知你覆盖了 a 所以我猜引擎将它保存在另一个内存空间中,而 b 仍然指向旧的 a 的内存地址(不知何故不会被破坏)。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-06-27
        • 2016-11-21
        • 1970-01-01
        • 1970-01-01
        • 2015-10-06
        • 2011-04-06
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多