- ベストアンサー
循環参照とメモリリークに関して
- 次のスクリプトはメモリリークを起こしているでしょうか。
- 実際にaddEventListenerのlistener引数に渡されるのは、element変数を参照しないfunction(evt){listener.call(evt.target,evt);};ですが、listener変数は参照します。そして、そのlistener変数はdiv変数(DOM)を参照するので、ここで循環するのでしょうか。
- var elements=[document.getElementsByTagName('div')[0]];elements[0].addEventListener('click',function(){;},false);elements[0].parentNode.removeChild(elements[0]);
- みんなの回答 (2)
- 専門家の回答
質問者が選んだベストアンサー
おはようございます。 >/* 使用 */ >(function(div){ > addEvent(div,'click',function(){;},false);//←ここ > div.parentNode.removeChild(div); >})(document.getElementsByTagName('div')[0]); addEvent(div,'click',function(){alert(div.tagName);},false); 上のようにすると、div 要素がスコープの範囲の中にあるを確認できるのでアウト! -- addEventListener があるのならば、removeEventListener があるのもご存知ですか? >div.parentNode.removeChild(div); をする前に、 var div = document.getElementsByTagName('div')[0]; var cbFunc = function () {;}; div.addEventListener ('click', cbFunc, false); //~ div.removeEventListener ('click', cbFunc, false); ←これを実行 div.parentNode.removeChild(div); のように本来するべきで、しかも汎用性をもとめて、関数 addEvent を定義するのでしょうから unload するときに、それらも行うべきなのでは? (結局、その手のものは、否定派です) -- >/* 定義 */ >(function(window,document,undefined){ > var process=function(listener){ > return function(evt){ > listener.call(evt.target,evt); > }; > }; > window.addEvent=function(element,type,listener,useCapture){ //←ここ2 > var callback=process(listener); > element.addEventListener(element,type,callback,useCapture); > return callback; > }; >})(this,this.document);//←ここ1 window から document を知ることができるし、またその逆も可。(1) var win = document.defaultView; (それらが違うHTML文書であるはずもないのですから) >window.addEvent(~ そこは結局、 this.addEvent(~ なのだから・・・ 関数 addEvevt の第1引数の HTML要素(div)だけれども、 var doc = div.ownerDocument; var win = doc.defaultView; とすると、そのHTML要素の親である document から window まで知ることができます。 -- 例えば、 <body> <div id="d0">abc</div> <div id="d1">def</div> <div id="d2">ghi</div> <script> document.addEventListener ('click', function (event) { var target = event.target; switch (target.id) { case 'd0': break; case 'd1': break; case 'd2': break; } }, false); </script> どの div が押されたかで処理を分岐できます。(親の document で管理できる) removeChild されようが、innerHTML で書き換えられようが、強固なコードになります。 イベントを削除するなんて気にする必要もありません。 <body> <div id="d0">abc</div> <div id="d1">def</div> <div id="d2">ghi</div> <script> var clickHandler = { div: document.querySelector ('div'), handleEvent : function (event) { var target = event.target; if (target === this.div) { //alert("ok!"); } }, init: function () { document.addEventListener ('click', this, false); } }; clickHandler.init (); </script> このように handleEvent メゾットを持つオブジェクトなら、それを気にする必要もなく・・・ 私は、難しいことは理解できませんが、面倒くさいか?そうでないのか?程度ならわかります。 たった1行で済む処理を複雑にする理由を見出せません。 document.getElementsByTagName('div')[0].addEventListener ('click', function () {;}, false);
その他の回答 (1)
- babu_baboo
- ベストアンサー率51% (268/525)
この手の質問は、苦手ですが恐れずに(漢字も含め)答えてみます。 間違いがあれば指摘が入るでしょうから。 >次のスクリプトはメモリリークを起こしているでしょうか。 まず、メモリーリークするのかは、ツールがあったりすのでそれで確認してください。 最近のブラウザでは、それは改善されているそうです。 「メモリーリークを起こしているのか」と「メモリーリーク・パターンなのか」とを 同じに考えないでください。 -- いわゆるクロージャーのなかで、グローバル変数に代入されたDOMは、そのパターンには 該当しません。 下は、メモリーリーク・パターンではありません。 function process (listener){ return function (evt){ listener.call (evt.target,evt); }; } 下は、メモリーリーク・パターンです。 function process (listener){ var doc = document; // documentは、グローバルですが doc そのものはノードを保存している return function (evt){ listener.call (evt.target,evt); alert (doc.nodeType); //ここのスコープから doc は参照出きるので、リークパターン。 }; } 下は、メモリーリーク・パターンではありません。 var doc; function process (listener){ doc = document; return function (evt){ listener.call (evt.target,evt); alert (doc.nodeType); }; } -- >function addEvent (element, type, listener, useCapture) { > element.addEventListener(element,type,process(listener),useCapture); >} 上の例では、「element」に、HTML要素が代入されて利用されます。 例えば、関数 listener から、スコープの外側の変数 element のノードタイプを 参照することはできますか? できるならアウトであり、不可能ならセーフです。 下は、関数 process を、関数 addEvent の中に含みました。 関数 listener からは、変数 element を参照できません。 function addEvent(element,type,listener,useCapture){ function process (listener){ alert (element.nodeType);//スコープとしてみれば参照出きるのでアウト。 return function (evt){ listener.call (evt.target,evt); }; } element.addEventListener(element,type,process(listener),useCapture); } 上では、alert (element.nodeType) として参照したからアウトではなくて、 参照できる環境を保持しているのでアウト。(いわゆるクロージャーとして内包しているから) -- >var div = document.getElementsByTagName('div')[0]; //存在するものとして >addEvent(div,'click',function(){;},false); //いかにも起こしそう >div.parentNode.removeChild(div); //親も存在するものとして 変数 div は、グローバル変数です。なのでセーフです。 「document.getElementsByTagName('div')」ここまでは、「生きたノードリスト」です。 「document.getElementsByTagName('div')[0]」これも「生きたノードリストの0番め」です。 ここで「生きた」とは、スクリプトが実行中 var div = document.getElementsByTagName('div'); alert(div.length); document.body.appendChild (document.createElement('div'));//追加 alert(div.length); などとすると HTMLドキュメントからみれば、DIV要素が増えたことになります。 これと同時に変数 div.length が勝手に増えてくれます。 つまり、 var div = [ document.getElementsByTagName('div')[0], document.getElementsByTagName('div')[1], document.getElementsByTagName('div')[2] ]; と、 var div = document.getElementsByTagName('div'); は、別物です。 上は「死んだノードリスト」であり、下は「生きたノードリスト」です。(勝手に命名) (function () { var div = document.getElementsByTagName('div'); for (var i = 0; i < div.length; i++) { div[i].onclick = function hoge () { alert(div[0].id); }; } })(); これは、関数 hoge から変数 div を参照できるので、メモリーリーク・パターンに見えますが DIV要素が追加されたり削除されたりする動的な環境であれば、div[0]をクリックしたからといって 必ずその div要素の id になるとは限りません。 なのでこれは、セーフです。 ちょっと話を戻して、 >addEvent(div,'click',function(){;},false); //いかにも起こしそう これも中の無名関数のスコープから、グローバル変数以外で参照できる変数に、 DOMノードが代入されていれば(参照可能であれば)アウト! >div.parentNode.removeChild(div); //親も存在するものとして 要素が削除された時点で、listener も削除されるのでセーフ。 ただし、document.createElement('div') され、イベントが取り付けられ、 なおかつ、そのノードが document に、append されていなければアウト! -- >function(evt){ > listener.call(evt.target,evt); >}; >ですが、listener変数は参照します。 >そして、そのlistener変数はdiv変数(DOM)を参照するので、ここで循環するのでしょうか。 この無名関数そのものが、DOMノードを保持していないのでセーフ。 (listener を呼び出すだけだから。) もしこれがメモリーリーク・パターンなら、イベントハンドラのプログラムでDOMを扱えません。 >そして、以下の場合はどうなのでしょうか。。 >var elements=[document.getElementsByTagName('div')[0]]; >elements[0].addEventListener('click',function(){;},false); >elements[0].parentNode.removeChild(elements[0]); 変数 elements はグローバル変数。 もし、上が何かの関数内なら死んだノードリスト。 もちろん、もう理解できましたよね。 -- 「イベント処理は、HTML要素個々に書くのではなく、 バブリングするイベントは、document で監視できるのだからそちらで書けっ!」 と私の偉人名言集に載っています。
お礼
補足 /* 定義 */ (function(window,document,undefined){ var process=function(listener){ return function(evt){ listener.call(evt.target,evt); }; }; window.addEvent=function(element,type,listener,useCapture){ var callback=process(listener); element.addEventListener(element,type,callback,useCapture); return callback; }; })(this,this.document); /* 使用 */ (function(div){ addEvent(div,'click',function(){;},false); div.parentNode.removeChild(div); })(document.getElementsByTagName('div')[0]); すみません。 訂正します。 こちらが正しいコードです。
補足
/* 定義 */ (function(window,document,undefined){ var process=function(listener){ return function(evt){ listener.call(evt.target,evt); }; }; window.addEvent=function(element,type,listener,useCapture){ element.addEventListener(element,type,process(listener),useCapture); }; )(this,this.document); /* 使用 */ (function(div){ addEvent(div,'click',function(){;},false); div.parentNode.removeChild(div); }})(document.getElementsByTagName('div')[0]); こんな風に使うとしたら、documentはローカル変数だから(?)、メモリリークパターンである、ということになるのでしょうか。(divの方は、elementに代入されて利用されるから、大丈夫?) 実際の所は、ちゃんとdocumentで振り分けていますが、新たな知識として、加えたいと思い、質問させて頂きました。 漢字での御回答ありがとうございます。
お礼
様々なご意見を頂き、大変参考になります。 ありがとうございます。 /* 使用 */ の部分は、実際に自分が書くわけではないので、メモリリークパターンになる可能性があるのですが、それをどうにかしようと、/* 定義 */の部分でもがいていました。(ただし、それぞれの要素に、イベントをそれぞれ付けるという条件付き。) しかし、やはりそれは無理だと感じることが出来ました。 よって、他の問題もあり、今後は個々の要素にイベントを付けることは極力避けるよう、気をつけようと思います。