web-dev-qa-db-ja.com

Javascriptポインター/参照の狂気。誰かがこれを説明できますか?

Javascriptはオブジェクトを参照渡しします。これは完全に理にかなっています。ただし、これらのオブジェクトの操作を開始すると、すべてが直感的でないように動作します。例を挙げましょう:

var a, b;

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

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

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

baへのポインタを持っているので、これはすべてうまくいきます。したがって、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への参照を失いますが、baが失われる前にaに設定された値を維持しているようですbへの参照。

しかし、私がこれを行うと:

a['two'] = 2;

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

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

何? abへの参照を明らかに失いましたが、baへの参照をまだ持っているようです。

空のオブジェクト{}はメモリ内のある場所を指しているので、それを参照するすべての変数は同じ場所を指しているのでしょうか?

これをしっかり理解している人がそれを私に説明できますか?

61
Philip Walton

サンプルを1行ずつ実行します。

a = {}

aは新しいオブジェクトを参照するようになりました。

b = a;

bは、aが参照する同じオブジェクトを参照するようになりました。 aを参照しないことに注意してください。

a['one'] = {};

新しいオブジェクトには、別の新しいオブジェクトを参照するインデックス'one'があります。

するとき

a = a['one'];

aa['one']を参照するように設定しています。これは、a['one'] = {}を実行したときに作成した新しいオブジェクトです。 bは、a = {}で作成したオブジェクトを引き続き参照します。

abへの参照を失いました」と言うと、abを参照せず、その逆も同じではないため、問題を混乱させています。 aおよびbobjectsを参照し、他のオブジェクトを参照するように作成できます。このような:

a = {}; b = aを使用すると、

a
 \
  \
   { }
  /
 /
b

次に、a['one'] = {}を使用して、

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

次に、a = a['one']を使用して、

a - - - - 
          \
   { one: { } }
  /
 /
b
202
Seth Carnegie

:Pあなたは細かいざらついた詳細に降りてきて、あなたが最後まで賢明になるので、私はあなたに尋ねてうれしいです。

ポインタの観点から見てはいけません、それはあなたが混乱しているところだと思うからです。ヒープ(または、必要に応じて「メモリ」)とシンボルテーブルの観点から考えてください。

コードの最初の数行から始めましょう。

var a, b;

a = {}
b = a;

ここで行ったことは、ヒープに1つのオブジェクトを作成し、シンボルテーブルに2つのシンボルを作成することです。次のようになります。


記号表

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


メモリの場所をたどるのに時間をかけると、ブラウザに正しい出力が表示されていることがわかります。

45
riwalk

無名オブジェクト自体に名前があると考えてください。

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.

重要なのは、変数がobjectsへの参照を保持し、他の変数への参照を保持しないことを覚えておくことです。また、同じオブジェクトが任意の数の変数によって参照される場合があります。

8
maerics

Javascriptのオブジェクトは、名前を必要とせずに単独で存在できます。例えば:

{}

辞書オブジェクトの新しいインスタンスです。

a = {};

新しい辞書オブジェクトを作成し、aがそれを参照するようにします。いま

b = a;

bが同じ基になるオブジェクトを参照するようにします。その後、aを別の場所に向けることができます。

a = "hi";

bは以前と同じ辞書オブジェクトを引き続き指します。 bの動作は、aが指すものを変更する方法とは無関係です。

6
Greg Hewgill

aを上書きしたことを知っている限り、エンジンは別のメモリ空間に保存していると推測しますが、bはまだ古いa 'を指していますsメモリアドレス(どういうわけか破棄されません)。

0
R01010010