元ネタは こちら をご参照ください。
IE8 における JavaScript でメモリーリークを起こすコードパターンです。 ここにあげているパターンはすべてパッチリリース済みなので、パッチをあてれば解消できます。 逆に、パッチがあてられない場合、絶対書いてはいけないコードです。
- innerHTML プロパティを頻繁に書き換える
- frameset 要素を使う
- window.open した先のウィンドウ内で循環参照を含む iframe 要素を使う
- window.createPopup した先のウィンドウ内で window.createStyleSheet を使う
- 循環参照があるスクリプトで、onload、onerror、onunload イベントハンドラーを接続する
- iframe から モーダルダイアログ を開いて、iframe を閉じる
innerHTML プロパティを頻繁に書き換える
この不具合の詳細は KB975623 を参照してください。
DOM要素の innerHTML プロパティを繰り返し書き換えるコードを Internet Explorer 8 で実行すると、 メモリリークします。
(あまりに、書いてしまいやすいコードすぎて涙が出てきます。。)
サンプルコード
var i, element element = document.getElementById('hoge'); for (i = 0; i < 5000; i++) { element.innerHTML = '' + (Math.random() * 100); }
frameset 要素を使う
frameset を利用したページの更新を繰り返すと、メモリリークします。 この不具合の詳細については KB982094 を参照してください。
今さら、 frameset ページのサンプルですが…、一応掲載します。 ちなみに、frameset要素、frame要素、noframes要素 は HTML5 で廃止されています。 「ユーザビリティやアクセシビリティに悪影響を及ぼすとみなされ」たためだそうです。
サンプルコード
index.html
<html> <head> <title>frameset sample page.</title> </head> <body> <frameset cols="50%,*"> <frame src="frame1.html" name="menu"></frame> <frame src="frame2.html" name="contents"></frame> <noframes> このページはフレーム対応のブラウザで閲覧ください。 </noframes> </frameset> </body> </html>
frame1.html
<html> <head> <title>frame1 - menu page.</title> </head> <body> <a href="frame2.html" target="contents">コンテンツ更新</a> </body> </html>
frame2.html
<html> <head> <title>frame2 - contents page.</title> </head> <body> <h1>コンテンツ</h1> </body> </html>
window.open した先のウィンドウ内で循環参照を含む iframe 要素を使う
この不具合の詳細については KB975736 を参照してください。 循環参照については こちら を参照してください。
サンプルコード
index.html
<html> <head> <script type="text/javascript"> var btn_onclick = function (event) { window.open('./childwindow.html'); }; window.lonload = function () { window.document.getElementById('btn').onclick = btn_onclick; }; </script> </head> <body> <input type="button" id="btn" value="子ウィンドウを開く" /> </body> </html>
childwindow.html
<html> <head> <style type="text/css"> iframe { position: absolute; top: 0; left: 0; right: 0; bottom: 0; } </style> </head> <body> <iframe src="./childcontent.html"></iframe> </body> </html>
childcontent.html
<html> <head> <script type="text/javascript" src="./childcontent.js"></script> </head> <body> <input type="button" id="setup" value="setup leak" /> <input type="button" id="teardown" value="teardown leak" /> <div id="foo"></div> <div id="bar"></div> </body> </html>
childcontent.js
// グローバル変数 var globalObject; // メモリリークするコードを呼び出し function setupLeak() { var element; // グローバル変数に DOM要素 への参照を保持 globalObject = document.getElementById('foo'); // "bar"要素の expandProperty に DOM要素 への参照を保持 element = document.getElementById('bar'); element.expandProperty = globalObject; }; // メモリリークしたオブジェクトを破棄して解放 function teardownLeak() { document.getElementById('bar').expandProperty = null; }; window.onload = function () { document.getElementById('setup').onclick = setupLeak; document.getElementById('teardown').onclick = teardownLeak; };
window.createPopup した先のウィンドウ内で window.createStyleSheet を使う
この不具合に関する詳細は KB2539352 を参照してください。 関連するメソッドの詳細は下記リンクをご参照ください。
サンプルコード
var html = ''; html += "<p>ポップアップ ウィンドウ の 内容</p>"; html += "<script>"; html += "var ss = document.createStyleSheet();"; html += "ss.addRule('body', 'background-color: gray;');"; html += "</script>"; // この関数を呼び出すとリーク(するはず…) var showPopup = function () { var windowObject = window.createPopup(); var body = windowObject.document.body; body.innerHTML = html; windowObject.show(100, 100, 300, 400); };
循環参照があるスクリプトで、onload、onerror、onunload イベントハンドラーを接続する
この不具合に関する詳細は KB2711084 を参照してください。 循環参照については こちら を参照してください。
循環参照がある JavaScript において、onload、onerror、onunload を使うとメモリリークするということは… JavaScript でクラスっぽい書き方をしたとき書きたくなる、下記のようなコードはダメと言うことだと思います。
サンプルコード
/** * @class * @constructor */ var SampleClass = function () { // 循環参照の原因となる DOM要素 this.window = window; this.document = window.document; this.body = window.document.body; // 初期化処理の実行 this.initialize(); }; /** * SampleClass の初期化処理 * NOTE: このメソッド内のクロージャで循環参照が起こっています。 */ SampleClass.prototype.initialize = function () { var self = this; this.body.onload = function (event) { self.onload(event); }; this.window.onunload = function (event) { self.onunload(event); }; this.window.onerror = function (message, url, line) { self.onerror(message, url, line); }; }; /** * onload の呼び出し */ SampleClass.prototype.onload = function () { }; /** * onunload の呼び出し */ SampleClass.prototype.onunload = function () { }; /** * onerror の呼び出し */ SampleClass.prototype.onerror = function (message, url, line) { }; // インスタンスの生成と実行 new SampleClass();
iframe から モーダルダイアログ を開いて、iframe を閉じる
この不具合に関する詳細は KB2695422 を参照してください。 showModalDialog 関数に関しては下記のリンクを参照してください。
サンプルコードを作っていて感じたのですが、このコードを実装することは少し難しい気がします。 モーダルダイアログを開くと親ウィンドウがさわれなくなるので、元の iframe を閉じづらい… おそらく、「window.open でモードレスダイアログにした上で、 window.showModalDialog のような実装が行われているのではないか」 という、個人的な意見です。
サンプルコード
index.html
<html> <head> <script type="text/javascript"> var removeContents = function (target) { var n, children; children = target.childNodes; for (n = children.length; n--;) { target.removeChild(target.firstChild); } }; var button_onclick = function (event) { var target = document.getElementById('content'); var iframe = document.createElement('iframe'); iframe.onload = function () { removeContents(target); } iframe.src = './content.html'; target.appendChild(iframe); }; window.onload = function () { document.getElementById('btn').onclick = button_onclick; }; </script> </head> <body> <div id="control"> <input type="button" id="btn" value="開く" /> </div> <div id="content"> </div> </body> </html>
content.html
<html> <head> <script type="text/javascript"> var url = 'http://www.yahoo.co.jp/'; window.showModalDialog(url, this); </script> </head> <body> iframe内 </body> </html>
…と、いろいろサンプルコードを書いていきましたが、実際にはすべてのコードを IE8 で評価していないので、間違っていたらごめんなさい。。
最後に… このブログに興味を持っていただけた方は、 ぜひ 「Facebookページ に いいね!」または 「Twitter の フォロー」 お願いします!!