質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

Q&A

解決済

3回答

434閲覧

クロージャーと参照について

murabito

総合スコア108

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

0グッド

0クリップ

投稿2018/04/14 03:23

編集2018/04/15 07:53
function outer() { var a = 1; var b = [{}, {}, {}, {}, {}]; return function inner() { console.log(a); a++; } } const fn = outer(); fn(); fn(); fn();

このコードの場合、outer()を呼んで返されるinner関数は外の変数aは参照し続けるけれども、変数bは参照をしていないので、変数bに代入されていた複数のオブジェクトを持つ配列に割り当てられたメモリは自動的に解放されるという理解であっていますでしょうか?

キャプチャー

イメージ説明

変数bは見当たらないので、そういうことなのかと思いました。

解決後の報告

Google Chrome Version 65.0.3325.181 (Official Build) (64-bit)で確認

function outer(lang) { var a = 1; var b = [{}, {}, {}, {}, {}]; if (lang === 'jp') { return function inner() { console.log(a); console.log(b); a++; } } else { return function inner() { console.log(a); a++; } } } const fn = outer('en');

このようなコードで試したところ、以下のように変数bを参照しない関数を返したとしても、変数aと変数bへの参照を持っていることを確認致しました。

イメージ説明

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

think49

2018/04/14 06:50

「解決後の報告」について。事後報告するのであれば、対象のブラウザ名、ブラウザのバージョンを明らかにするようお願いします。実装依存なので、どこまで参考になる情報なのか判断できなくなっています(IE11等の他のブラウザで再現出来なかったり、同じブラウザでもバージョンアップで挙動が変わる事はありえます)。
murabito

2018/04/15 07:53

「Google Chrome Version 65.0.3325.181 (Official Build) (64-bit)で確認」と追記いたしました。
guest

回答3

0

ベストアンサー

ガベージコレクション (Garbage Collection)

JavaScript

1function outer() { 2 3 var a = 1; 4 var b = [{}, {}, {}, {}, {}]; 5 6 return function inner() { 7 console.log(a); 8 a++; 9 } 10 11} 12 13const fn = outer();

関数 inner が生きている限り、関数スコープ inner から参照可能な変数は全てメモリに残り続けます。
従って、a, b ともにメモリから解放されません。

ただし、関数 inner がどこからも参照されなくなった場合は、変数 a, b を参照する対象(関数)がなくなる為、ガベージコレクション(GC)によって、a, b 共にメモリから解放されます。

変数 b が見当たらない

変数bは見当たらないので、そういうことなのかと思いました。

ESLint では "'b' is assigned a value but never used." の警告が発生しますね。

Closure Compilerは b の変数文を削除しますし、Google Chrome は変数 b を初めから宣言しない最適化が施されていたとしても不思議ではありません。

元々、メモリの開放処理は標準仕様にはない動作ですので、ブラウザ毎に実装方法が異なります。

Re: murabito さん

投稿2018/04/14 04:07

編集2018/04/14 05:04
think49

総合スコア18156

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

think49

2018/04/14 04:31

ただ、「変数 b はどこからも参照視されていない」ので、変数 b をメモリから解放する実装がある可能性は否定できませんね…。 一般的には親記事の回答で問題ないと思いますが、ブラウザは最適化を続けているので、オープンソースな Chromium, Firefox のコードを読んでみるといいかもしれません(実装依存です)。
defghi1977

2018/04/14 06:53

スコープからの参照が残っている以上, ガベージ対象とは出来ないと思います.(テンプレートリテラルやevalからの変数参照が可能なので.)
think49

2018/04/14 06:57

> テンプレートリテラルやevalからの変数参照が可能なので. こちらが理解できなかったのですが、実証コードを書いて頂くことは可能でしょうか。
defghi1977

2018/04/14 07:02

例えば function outer() { var b = [{}, {}, {}, {}, {}]; return function inner(src) { return eval(src); } } const fn = outer(); console.log(fn("b").length); とかなってると, bを開放して良いもんだか悪いもんだか機械的には判断できない気が.
think49

2018/04/14 07:16

なるほど、確かに。 eval() が存在したら変数を残しておく手しか思いつきませんでした。 ちなみに、Closure Compilerは変数文を削除し、ReferenceError を誘発させました。 https://closure-compiler.appspot.com/home eval is evilでしょうか。 (0,eval)(src); や Function(src)() で封じられるローカル変数参照は考慮されていないようです。
defghi1977

2018/04/14 07:19

少なくとも「evalなんか使ってたら逆立ちしたって最適化出来ね―」ってことでしょうね
guest

0

意味合いとしては「プログラムからは参照されることがない」のだからgcが回収してくれるだろうと期待したくなりますが、他のお二人が回答しておられるように現状の実装ではクロージャー内部で明示的に参照されてなくても参照される可能性がある変数は全て「暗黙に覚えられてしまう」ようですね。

http://www.ecma-international.org/ecma-262/6.0/#sec-lexical-environments

この辺りを読むと「変数などの識別子とその値の対応を保持するEnvironment Record」なるものが解説されてます。「クロージャーの中のコードが実際に参照するかどうか」とは別に宣言されている変数は全てEnvironment Recordから見えてしまう」実装になっているのかどうかはこれだけ見ても自分にははっきりわかりませんでしたが、少なくとも何らかの最適化によりouterが最適化されない限りはEnvironment Recordに不要な変数が存在し続けているような気がします。

しかし、「実際どうなのか」を見た方が単純に納得しやすい(自分自身が)と思ったので実際node 9.0.0でやってみました。

gc.js

javascript

1const process = require('process') 2const linkClosure = process.argv[2] !== void 0 3const linkExplicitly = process.argv[2] === 'explicitly' 4const N = 64 5const GARBAGE_SIZE = 0x10000 6 7function foo() { 8 let a = {next:null, link:null, garbage:null} 9 let garbage = Buffer.alloc(GARBAGE_SIZE) 10 11 totalAllocated += GARBAGE_SIZE 12 13 const f = () => { 14 if (linkClosure) a.link = f; 15 if (linkExplicitly) a.garbage = garbage 16 return a 17 } 18 return f 19} 20 21console.log(`linkClosure = ${linkClosure}, linkExplicitly = ${linkExplicitly}`) 22 23let top 24let totalAllocated = 0 25for (let i = 0; i < 4096; i++) { 26 const f = foo() 27 const a = f() 28 a.next = top 29 top = a 30} 31const mu = process.memoryUsage() 32const report = ['rss', 'heapTotal', 'heapUsed', 'external'] 33 .map(k => fmt(mu[k])).join('/') 34console.log(`${fmt(totalAllocated)}: ${report}`) 35 36function fmt(n) { 37 const s = n.toString(16) 38 return " ".repeat(12 - s.length) + s 39}

bash

1$ node gc 2linkClosure = false, linkExplicitly = false 3 10000000: 5605000/ 954000/ 456ec8/ 7fd2020 4$ node gc closure 5linkClosure = true, linkExplicitly = false 6 10000000: 11905000/ a54000/ 554b88/ 10002020 7$ node gc explicitly 8linkClosure = true, linkExplicitly = true 9 10000000: 11908000/ a54000/ 554c60/ 10002020

最初の列は実験対象のオブジェクト(garbage)のトータルの確保量、それ以降の列はprocess.memoryUsageのrss,heapTotal,heapUsed,externalの値です。garbageはBuffer.allocateで確保するようにしてますがこの領域はC++領域(external)に確保されるようです。結果をみるとクロージャーを覚えているだけ(node gc closureの実行)でもgarbageがgcで回収されてないことがわかります。


自分はアマチュアのため「うーん仕様をみてもわからん」という場合、割と安直に実験しようとします。プロの方だと「複数のエンジンに共通な仕様が何か」というもっと上のレベルのことを気にされていると思いますが、javascriptの仕様を把握するのはさぞ大変だろうと常々思います。

投稿2018/04/14 06:48

KSwordOfHaste

総合スコア18392

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

変数bに代入されていた複数のオブジェクトを持つ配列に割り当てられたメモリは自動的に解放されるという理解であっていますでしょうか?

いいえ,間違っています.

変数a,bを保持しているスコープを関数が参照している限り, 変数bを直接的に操作していないとは言え, 勝手にその内容がメモリから開放されることはありません.

投稿2018/04/14 03:37

defghi1977

総合スコア4756

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問