最近、DOMオブジェクトとクロージャにより循環参照が起き、メモリリークが発生するという問題について知りました。
この問題が古いブラウザで発生し、動作に影響すると知ったため、昔書いたクロスブラウザ対応コードにこの問題が無いか検証したところ、以下の様な循環参照パターンとなっている事が分かりました。
(以下に提示するのは、簡易化したコードです)
JavaScript
1/** 2 * @param {Node} targetNode 3 */ 4function clickCaptcha(targetNode) { 5 targetNode.addEventListener('click', function () { 6 console.log(targetNode); 7 }, false); 8}
targetNode
が操作対象のDOMノードです。
これに追加するリスナーがクロージャになっており、targetNode
を参照しています。
このコードを循環参照が発生しないものに書き換えようと考えていますが、その方法が分かりません。
単純に考えるならば、以下のようにすれば解決すると思います。
JavaScript
1/** 2 * @param {Node} targetNode 3 */ 4var clickCaptcha = (function () { 5 // イベントのリスナーを外部に出すことでクロージャを回避 6 var clickListener = function () { 7 // addEventListenerにより設定したリスナーは、 8 // thisがイベントを追加した要素のDOMノード(=targetNode)となる 9 console.log(this); 10 }; 11 12 return function (targetNode) { 13 targetNode.addEventListener('click', clickListener, false); 14 }; 15}());
JavaScript
1/** 2 * @param {Node} targetNode 3 */ 4var clickCaptcha = (function () { 5 // イベントのリスナーを外部に出すことでクロージャを回避 6 var clickListener = function (e) { 7 // EventオブジェクトのcurrentTargetプロパティを利用 8 console.log(e.currentTarget); 9 }; 10 11 return function (targetNode) { 12 targetNode.addEventListener('click', clickListener, false); 13 }; 14}());
しかし、クロスブラウザ対応用のコードであるため、addEventListener
ではなくattachEvent
を使用した場合はthis
が扱えませんし、Eventオブジェクトも信用できません。
このため、この2つの解決方法は採用できず、変数targetNode
を他の方法でリスナーから参照する必要があります。
【クロージャとメモリリークについてのコピペ - こんにちはこんにちはmonmonです!】の702で提示されたcreateLeakFreeClosure
関数を使用し、以下のように書く方法もあります。
JavaScript
1function createLeakFreeClosure(closure) { 2 /** 3 * Note: 適切に動作するよう、いくつか修正 4 */ 5 var count = createLeakFreeClosure.count++; 6 createLeakFreeClosure[count] = closure; 7 closure = null; 8 return function () { 9 return createLeakFreeClosure[count].apply( 10 this, 11 Array.prototype.slice.call(arguments) 12 ); 13 }; 14} 15createLeakFreeClosure.count = 0; 16 17/** 18 * @param {Node} targetNode 19 */ 20function clickCaptcha(targetNode) { 21 targetNode.addEventListener('click', createLeakFreeClosure(function () { 22 console.log(targetNode); 23 }), false); 24}
しかし、createLeakFreeClosure
関数は同ページの707でも指摘されているように、何度も実行した場合に引数に渡した関数が保持され続ける事になり、これはこれで問題となってしまいます。
私自身もこの関数が循環参照を回避できる理由が理解できていないため、下手に改良もできません。
よって、これも採用できません。
冒頭のコードを、循環参照が発生せず、また変数targetNode
を参照できるようにするには、どのようにすれば良いでしょうか。
循環参照について理解できていないため、動作の違うサンプルを例示されても修正して活用できません。
このため、コメントにて私の書いたコードを例示し、何度も指導していただく形になってしまいます。
可能であれば、「第一引数に受けたDOM Nodeにclickイベントを定義」する、質問本文冒頭のコードと同じ動作をする循環参照を解決したコードを例示していただければ有難いです。