あなたは長い間ウェブアプリケーションを書いてきましたが、すでにすべてを見たことがあり、他のことを恐れていませんか? それから、明かりを消して近くに座って、夜のおとぎ話を話したいです。
1つの大都市、1つの大企業、IT企業で、1つの大規模プロジェクトを1つの非常に使用されているブラウザーでテストしました。 そして、彼らはそこでメモリリークを発見しました。 大きい、大きい。 リリースの直前。
そして、開発者が完全に馬鹿だったとしても驚くことではありません。 しかし、開発者は「Internet Explorerのリークパターンの理解と解決」を心から知っていました。 円形リンクが壊れ、クロージャーは使用されず、イベントは適切に扱われ、ハンドラーはそれらを削除することを忘れませんでした。 はい、それは漏れから救われなかっただけです。
Discalimer:下記のエンティティ、イベント、コードスニペットはすべてフィクションです。 すべての一致はランダムです。
さて、良い仲間は何をしましたか? もう一度、彼らは座ってコンポーネントを調べ、何かを見逃しているかどうかを確認し、さまざまなプログラムを実行し始め、Googleの答えを試してみました。 しかし、プログラムは役に立たず、賢明なグーグルは黙っていました。 感染症が潜んでいる場所を探すために、私は袖を一列ずつ巻かなければなりませんでした。 そして、悪意のある行が見つかり、次のようになりました。
var cell = tableEl.firstChild.rows[0].cells[0];
そして、開発者は途方に暮れていて、問題を研究して解決しなければなりませんでした。 彼らが管理したもので。
そして、おとぎ話が終わり、問題を示すコードが始まります:
<!DOCTYPE HTML>
<html>
<body>
<span id="count">0</span> \<input id="num" value="1000" /\>
<input type="button" value="GO!" onclick="execute()" />
<hr />
<div id="test"></div>
</body>
<script type="text/javascript">
var count = 0;
function execute()
{
var val = document.getElementById('num').value;
for (var i = 0; i < val; i++)
{
var domEl = document.getElementById('test');
domEl.innerHTML = '<table><tbody><tr><td>A1</td></tr><tr><td>B1</td></tr></tbody></table>';
domEl.firstChild.insertRow(0);
domEl.removeChild(domEl.firstChild);
count++;
}
document.getElementById('count').innerHTML = count;
};
</script>
</html>
Process Explorerを起動し、「GO!」を数回押します-押すと10メガバイトで実行されます:
リークは、
domEl.firstChild.rows[0]
するだけでわかるように、
domEl.firstChild.rows[0]
行に隠されています。 さらに、バグはIE8標準モードでのみ表示され、IE7およびIE8 Quirksモードはこの病気の影響を受けません。
domEl.firstChild.firstChildまたはgetElementByIdを置き換えると、リークを排除できますが、プロジェクト全体にこのような行が何百もある場合はどうでしょうか? たぶん、時間のかからないオプションがありますか?
マイクロソフトのインド人がどのようにしてこの効果を達成したかを理解してみましょう。 明らかに、列とセルのコレクションは使用時にのみ作成され、ソリューションは非常に堅牢です。 また、テーブルがドキュメントツリーのDOMから切り離されても、それらは破棄されません-それも非常に理解可能で合理的です。 ここでのみ、何らかの理由でガベージコレクターで、結果の循環リンクを考慮するのを忘れていました。
仮説を確認できます。 コレクションの作成に関するものである場合、インデックスが使用される他のケースでリークが発生します。 行[0]をinsertRow(0)に置き換えます。 ビンゴ! リークは保存されました。
今、闘争の方法は明確です。 DOM要素TBODYとTDの間に循環リンクが配置されているため、TBODYのすべての子孫を削除するだけです。
while(domEl.firstChild.firstChild) {
while(domEl.firstChild.firstChild.firstChild)
domEl.firstChild.firstChild.removeChild(domEl.firstChild.firstChild.firstChild);
domEl.firstChild.removeChild(domEl.firstChild.firstChild);
}
それは機能しましたが、幸福にはまだ十分ではありません。コンポーネント内のテーブルを正しく削除することは別に面倒であり、しばらくの間何かを忘れてしまいます。 もちろん、プロセスを自動化し、getElementsByTag( 'TABLE')のようなものを使用することもできます。しかし、すべてを行う魔法のプロパティinnerHTMLがあるため、必要ではありません。
domEl.removeChild(domEl.firstChild)
を
domEl.innerHTML = ''
置き換えます
チェックします。 動作します。
要約すると:
このタイプのリークは文書化されておらず、行または列のインデックスを使用してDOMテーブルオブジェクトのメソッドとプロパティを使用する場合にIE8標準モードでのみ発生します。 (.rows [] 、. cells []、insertRow()など)
最善の回避策は、これらのメソッドとプロパティを使用しないことです。
別の方法は、テーブル内のすべての行のDOM要素がそれらの祖先から切断されていることを確認するか、最も単純なオプションを使用することです:
innerHTML = ''