解決しました
この主題に関して、ウェブ上には多くの矛盾した情報があります。@Johnのおかげで、クロージャ(以下で使用)はメモリリークの原因ではなく、IE8でも、人々が主張するほど一般的ではないことがわかりました。実際、私のコードで発生したリークは1つだけでしたが、修正するのはそれほど難しくありませんでした。
これから、この質問に対する私の答えは次のようになります
。IE8がリークするのは、イベントがアタッチされたとき、またはグローバルオブジェクトにハンドラーが設定されたときだけです。(window.onload
、、window.onbeforeunload
...)。これを回避するには、以下の私の答えを参照してください。
巨大な更新:
私は今完全に迷子になっています...古いものと新しいものの両方の記事やタットを掘り下げた後、私は少なくとも1つの大きな矛盾を残されています。JavaScriptの達人の1人(ダグラス・クロックフォード)は次のように述べています。
IEはその仕事をしてサイクルを取り戻すことができないので、それを行うのは私たちの責任です。明示的にサイクルを中断すると、IEはメモリを再利用できるようになります。Microsoftによると、クロージャはメモリリークの原因です。もちろんこれは非常に間違っていますが、MicrosoftがMicrosoftのバグに対処する方法についてプログラマーに非常に悪いアドバイスを与えることになります。DOM側でサイクルを壊すのは簡単であることがわかります。JScript側でそれらを壊すことは事実上不可能です。
また、@ freakishが指摘したように、以下のスニペットはjQueryの内部動作に類似しているため、メモリリークを引き起こさないソリューションについてはかなり安全だと感じました。同時に、このMSDNページを見つけました。このセクションCircular References with Closures
は私にとって特に興味深いものでした。次の図は、私のコードがどのように機能するかをほぼ概略的に表したものです。
唯一の違いは、イベントリスナーを要素自体にアタッチしないという常識があることです。
同じDouggieは非常に明確です。つまり、クロージャはIEのmem-leaksの原因ではありません。この矛盾は私に誰が正しいかについて無知なままにします。
また、IE9でもリークの問題が完全には解決されていないこともわかりました(リンクATMが見つかりません)。
最後にもう1つ<select>
、IEがJScriptエンジンの外部でDOMを管理していることもわかりました。これにより、ajaxリクエストに基づいて、要素の子を変更するときに煩わしい思いをすることになります。
function changeSeason(e)
{
var xhr,sendVal,targetID;
e = e || window.event;//(IE...
targetID = this.id.replace(/commonSourceFragment/,'commonTargetFragment');//fooHomeSelect -> barHomeSelect
sendVal = this.options[this.selectedIndex].innerHTML.trim().substring(0,1);
xhr = prepareAjax(false,(function(t)
{
return function()
{
reusableCallback.apply(this,[t]);
}
})(document.getElementById(targetID)),'/index/ajax');
xhr({data:{newSelect:sendVal}});
}
function reusableCallback(elem)
{
if (this.readyState === 4 && this.status === 200)
{
var data = JSON.parse(this.responseText);
elem.innerHTML = '<option>' + data.theArray.join('</option><option>') + '</option>';
}
}
IEが実際にJScriptエンジンがないかのようにDOMを管理している場合、このコードを使用してオプション要素の割り当てが解除されない可能性はどのくらいありますか?
このスニペットを例として意図的に追加しました。この場合、クロージャースコープの一部である変数をグローバル関数の引数として渡すためです。この方法に関するドキュメントは見つかりませんでしたが、Miscrosoftから提供されたドキュメントに基づくと、発生する可能性のある循環参照が壊れているはずです。
警告:長い質問...(ごめんなさい)
WebアプリケーションでAjax呼び出しを行うために、かなり大きなJavaScriptをいくつか作成しました。大量のコールバックとイベントを回避するために、イベントの委任とクロージャを最大限に活用しています。これで、メモリリークの可能性について疑問に思う関数を作成しました。IE> 8は、以前のバージョンよりもはるかに優れたクロージャーを処理することを私は知っていますが、IE8をすべて同じようにサポートすることは会社の方針です。
以下に、私が取り組んでいる例を示します。ここでは、同様の例を見つけることができますが、ajaxは使用していませんが、setTimeoutを使用すると、結果はほとんど同じです。(もちろん、以下のコードをスキップして、質問自体に進むことができます)
私が念頭に置いているコードは次のとおりです。
function prepareAjax(callback,method,url)
{
method = method || 'POST';
callback = callback || success;//a default CB, just logs/alerts the response
url = url || getUrl();//makes default url /currentController/ajax
var xhr = createXHRObject();//try{}catch etc...
xhr.open(method,url,true);
xhr.setRequestMethod('X-Requested-with','XMLHttpRequest');
xhr.setRequestHeader('Content-type','application/x-www-form-urlencoded');
xhr.setRequestHeader('Accept','*/*');
xhr.onreadystatechange = function()
{
callback.apply(xhr);
}
return function(data)
{
//do some checks on data before sending: data.hasOwnProperty('user') etc...
xhr.send(data);
}
}
コールバックを除いて、すべての非常に単純なものonreadystatechange
。ハンドラーを直接バインドするときにIEにいくつかの問題があることに気づきましたxhr.onreadystatechange = callback;
。つまり、無名関数です。理由はわかりませんが、これが最も簡単な方法であることがわかりました。
私が言ったように、私は多くのイベント委任を使用しているので、ajax呼び出しを起動した実際の要素/イベントにアクセスできると便利であることがわかるかもしれません。したがって、次のようなイベントハンドラーがいくつかあります。
function handleClick(e)
{
var target,parent,data,i;
e = e || window.event;
target = e.target || e.srcElement;
if (target.tagName.toLowerCase() !== 'input' && target.className !== 'delegateMe')
{
return true;
}
parent = target;
while(parent.tagName.toLowerCase() !== 'tr')
{
parent = parent.parentNode;
}
data = {};
for(i=0;i<parent.cells;i++)
{
data[parent.cells[i].className] = parent.cells[i].innerHTML;
}
//data looks something like {name:'Bar',firstName:'Foo',title:'Mr.'}
i = prepareAjax((function(t)
{
return function()
{
if (this.readyState === 4 && this.status === 200)
{
//check responseText and, if ok:
t.setAttribute('disabled','disabled');
}
}
})(target));
i(data);
}
ご覧のとおり、onreadystatechange
コールバックは関数の戻り値でありtarget
、コールバックが呼び出されたときに要素への参照を提供します。イベントの委任のおかげで、DOMから削除することを決定したときに、その要素にバインドされる可能性のあるイベントについて心配する必要がなくなりました(私は時々そうします)。
しかし、私の考えでは、コールバック関数の呼び出しオブジェクトは、IEのJScriptエンジンとそのガベージコレクターにとってはあまりにも多くのことを証明するかもしれません。
イベント==>ハンドラー==>prepareAjaxはごく普通の呼び出しシーケンスですが、コールバック引数は次のとおりです。
[アノン。func(引数t = target)はanonを返します。F(tにアクセスし、ターゲットに参照します)]
===> xhrオブジェクトに.applyメソッドを使用して呼び出され、prepareAjax関数にプライベート変数を使用して呼び出されるanonコールバック関数に渡されます
私はこの「構造」をFFとクロムでテストしました。そこでは問題なく動作しますが、クロージャのクロージャのこの種のコールスタックは、そのたびにDOM要素への参照を渡すことがIE(特にIE9より前のバージョン)で問題になりますか?
いいえ、jQueryやその他のライブラリは使用しません。私は純粋なJSが好きで、この深刻に過小評価されている言語についてできるだけ知りたいと思っています。コードスニペットは実際のコピーアンドペーストの例ではありませんが、スクリプト全体で委任、クロージャ、コールバックをどのように使用しているかを示すIMOを提供します。したがって、構文が正しくない場合は、自由に修正してください。ただし、もちろん、それはこの質問の内容ではありません。