質問: 'piyopiyo' // c console.log(piyo); // d } foo(); // 正常に動く // e console.log(piyo); 参照エラー // f ``` とあった場合、 各変数は評価された時点でメモリに割当られるんですよね。 そしてfの時点ではメモリから解放されているんでしょうか。 参考ページ > > JavaScriptオブジェクトについては、ガベージコレクタ方式が採用されています。 > > ガベージコレクタは以下のタイミングでメモリの開放を行います。 > > > * 変数に対し、明示的にnullをセットした時 > > * 関数の実行終了時 > > > 上記以外は任意のタイミング(JavaScriptエンジンによっては、ガベージコレク>タを呼出すメソッドが用意されている) > > > fの地点でエラーになるのはスコープの問題なのかなって気もしますが。 「メモリに変数が割り当てられているかどうか?」 を確認する術はあるのでしょうか? Chromeの開発ツールを使って検証したのですがいまいちわかりません。 ![イメージ説明](https://teratail.storage.googleapis.com/uploads/contributed_images/30d644cc263ee883deb82704de292c8c.png) ![イメージ説明](https://teratail.storage.googleapis.com/uploads/contributed_images/841717a622ed6005d95c0cff0128e726.png) 気になってしまって他の業務がストップしてしまって、、、 どなたかご教授いただけませんでしょうか。 どうぞ宜しくお願い致します。
回答: ### メモリが解放されるタイミングはわからない。 JavaScript(ECMAScript)の仕様では、いつメモリが解放されるのかは定められていません。GC(ガベージコレクション)がいつ動作するのか、また、そのGCで回収されるのかはJavaScriptエンジンによって異なり、一概には言えません。また、一言でGCと言っても参照カウント方式からM&S方式、世代別GC、インクリメントGCなど多くの方式があり、高度な実装ではそれらが複雑に混在する形になっています。 よって、JavaScriptにおいては、特定の実装について言及していない限り、「○○した時にメモリが解放される」と書かれている物は**すべて間違い**です。`null`をセットしようが、関数が終了しようが、`delete`を使おうが、メモリが解放される保証は一切ありません。 GCについて保証される動作はただ一つです。ルート(一番上にあるトップレベルのオブジェクト)から参照を辿って到達できるメモリ領域は絶対に解放されない、ということだけです。 ※ WeakMap等を用いた**弱参照**という特殊な参照でしかたどり着けない領域は解放される場合があります。 ### 「変数」の寿命と「オブジェクト」の寿命は異なる。 メモリがいつ解放されるのかがわからないといっても、GCが回収できる状態になっているかどうかを把握することは重要です。もし、全てが参照されたままでオブジェクトが増えていった場合、GCが動作しても、メモリが解放されずにいつかは破綻してしまいます。いわゆるメモリリークと言われる問題です。 ※ GCがあればメモリリークが起きないわけではありません。メモリリークが起きる不具合のパターンが減るだけです。 さて、プログラムにおいて、メモリ上のデータは生成と破棄を繰り返しています。いつ破棄されるのか(GCに回収されるのか)はわかりませんが、その時点でプログラム上から見えない物になっていますので、コーディング上は破棄された物として扱うべきでしょう。こういった生成してから破棄され(ても問題なくなった状態にな)るまでの期間をここでは**寿命**といって説明します。 ※ ここでの寿命はメモリ上の寿命の事を指しているわけではありませんが、言語によってはメモリ上にデータがあるかどうかを寿命という場合があります。適当な言葉がなかったため今回は「寿命」という言葉を使っていますが、文脈によっては必ずしも正しいとは限りません。 ``` var x = "abc"; // 1 ``` 1のとき変数`x`が生成されました。また、文字列"abc"も生成されました。変数`x`は先ほど生成された文字列"abc"をを参照するようになりました。 ん、何かおかしいことを言いましたか?一つの文に二つの生成物?そう、ここで重要なのは二つ生成されると言うことです。 ※ 数値、真偽値、null、undefinedは少し特殊で、実装上はオブジェクトとして生成されるわけではありません。文字列も少し特殊なのですが、オブジェクトとして生成されるとしています(ただし、プリミティブ型であるため、Objectを継承したオブジェクトではありません)。配列やObjectの方がイメージしやすいかも知れません。 ``` var x = "abc"; // 1 x = "012"; // 2 ``` 2の時、文字列"012"が生成されました。そして変数`x`は先ほど生成された文字列"012"を参照するようになりました。**文字列"abc"の寿命が尽きました。** 変数`x`は相変わらず存在します。しかし、文字列"abc"はもはや参照してくれる物はありません。どのような手段を用いてもその文字列に辿り着くことができないため、やがてGCによって回収されます。これは変数`x`の寿命がどうなっているかとは無縁です。 もう何が何だかわからないと思っているかも知れません。重要なのは変数`x`とはなんなのかです。JavaScriptにおいて変数とはスコープ(環境)に結びつけられたものです。いきなり何を...と思うかも知れません。スコープは自分が変数のリストを持っています。この変数一つ一つが何かしらのオブジェクトを参照しています(参照先がundefinedの場合もあります)。今トップレベルで書いている上のコードにおいて、変数`x`はトップレベルのスコープの変数リストにある一つの変数です。トップレベルが生きている限り、変数`x`も生き続けます。 ちょっと関数の場合を見てみましょう。 ``` function f() { var x = "abc"; // 1 return x; // 2 } var y = f(); // 3 ``` このコードにおいて、変数`x`は関数`f()`のスコープ(関数スコープ)が持っている物です。この関数スコープは関数を実行する度に生成され、その度に変数`x`は生成されます(1)。1では同時に文字列"abc"も生成します。文字列"abc"は変数`x`を介して関数の戻り値として返っており(2)、変数`y`に代入、つまり、変数`y`の参照先が文字列"abc"になります(3)。ここで重要なのは、1で生成された文字列"abc"と3で変数`y`の参照先となる文字列"abc"が**同一**であるとうことです。ここでの**同一**は同じ内容の文字列になるという意味ではなく、メモリ上も全く同じ所になるという意味です。 3のとき、文字列"abc"は変数`y`から参照されています。ですので、文字列"abc"はGCで回収されず、まだ生きています。対して、変数`x`を見てみましょう。関数スコープは関数が終了した場合、*そのスコープを参照している他の生きているスコープがなければ*、寿命が尽きます。スコープの寿命が尽きれば、そのスコープが持っていた変数も寿命が尽きます。ということで、2が終わって、3に戻った段階で、変数`x`は寿命が尽きていることになります。いずれGCに回収されるでしょう。 おっと、気をつけて欲しいのは変数`x`の寿命が尽きただけで、変数`x`の参照先の寿命が尽きたというわけでは無いと言うことです。文字列"abc"は既に変数`y`からも参照されているため、変数`x`がなくなっても、辿ることができる参照はなくなっていません。相変わらず、文字列"abc"は生き続けます。 #### 【おまけ】クロージャーで変数は寿命が延びる スコープの寿命についての所で「*そのスコープを参照している他の生きているスコープがなければ*」と書いてあったのを覚えていますか?何を言っているのだろうと思ったのかも知れませんが、これはクロージャーの仕組みのことです。JavaScriptにはスコープチェーンという仕組みがあり、入れ子になったスコープは親のスコープへの参照を持ちます。クロージャーはスコープごと持ち出す仕組みであるため、この参照もそのまま持ち出すことができます。そうなると、持ち出されたクロージャーの寿命が尽きない限り、参照されたスコープの寿命も尽きず、その中の変数の寿命も尽きません。 ``` function f() { var x = "abc"; return function() { x += "+"; return x; }; } var g = f(); console.log(g()); // abc+ console.log(g()); // abc++ console.log(g()); // abc+++ ``` 変数`x`は関数`f()`が終わった後も生き続けます。変数`g`が参照している関数のスコープが関数`f()`を実行したスコープを参照し続けるからです。なので、その後の呼び出しても、`x`は使用し続けることができます。