元ネタは こちら をご参照ください。
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 で評価していないので、間違っていたらごめんなさい。。