元ネタは こちら をご参照ください。
IE8 における JavaScript でメモリーリークを起こすコードパターンです。 ここにあげているパターンはすべてパッチリリース済みなので、パッチをあてれば解消できます。 逆に、パッチがあてられない場合、絶対書いてはいけないコードです。
- innerHTML プロパティを頻繁に書き換える
- frameset 要素を使う
- window.open した先のウィンドウ内で循環参照を含む iframe 要素を使う
- window.createPopup した先のウィンドウ内で window.createStyleSheet を使う
- 循環参照があるスクリプトで、onload、onerror、onunload イベントハンドラーを接続する
- iframe から モーダルダイアログ を開いて、iframe を閉じる
innerHTML プロパティを頻繁に書き換える
この不具合の詳細は KB975623 を参照してください。
DOM要素の innerHTML プロパティを繰り返し書き換えるコードを Internet Explorer 8 で実行すると、 メモリリークします。
(あまりに、書いてしまいやすいコードすぎて涙が出てきます。。)
サンプルコード
1 2 3 4 5 | 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | < 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
1 2 3 4 5 6 7 8 | < html > < head > < title >frame1 - menu page.</ title > </ head > < body > < a href = "frame2.html" target = "contents" >コンテンツ更新</ a > </ body > </ html > |
frame2.html
1 2 3 4 5 6 7 8 | < html > < head > < title >frame2 - contents page.</ title > </ head > < body > < h1 >コンテンツ</ h1 > </ body > </ html > |
window.open した先のウィンドウ内で循環参照を含む iframe 要素を使う
この不具合の詳細については KB975736 を参照してください。 循環参照については こちら を参照してください。
サンプルコード
index.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | < 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | < 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
1 2 3 4 5 6 7 8 9 10 11 | < 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | // グローバル変数 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 を参照してください。 関連するメソッドの詳細は下記リンクをご参照ください。
サンプルコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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 でクラスっぽい書き方をしたとき書きたくなる、下記のようなコードはダメと言うことだと思います。
サンプルコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | /** * @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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | < 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
1 2 3 4 5 6 7 8 9 10 11 | < 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 の フォロー」 お願いします!!