Javascript指针/引用的疯狂.有人能解释一下吗?

Javascript pointer/reference craziness. Can someone explain this?

本文关键字:一下 能解释 指针 引用 疯狂 Javascript      更新时间:2023-09-26

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的引用。

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

能不能找个对这个有深刻理解的人给我解释一下?

逐行执行示例:

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

: p你正在深入到编织粗糙的细节中,我很高兴你问了,因为最后你会更明智。

不要从指针的角度看它,因为我认为这是你困惑的地方。可以从堆(或者只是"内存")和符号表的角度来考虑它。

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

var a, b;
a = {}
b = a;

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


符号表:

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

:

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

.


事情变得有趣了:对象有自己的"符号表"(通常这些只是散列表,但称其为符号表会更清楚)。

现在,在下一个语句之后,您有3件事情要考虑:全局符号表、<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指向包含<object val 2>的内存位置0x400004
  • <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.

.


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

认为匿名对象本身有一个名称:

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.
关键是要记住变量保存对对象的引用,而不是对其他变量的引用。并且同一个对象可以被任意数量的变量引用。

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

{}

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

a = {};

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

b = a;

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

a = "hi";

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

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