• ベストアンサー
※ ChatGPTを利用し、要約された質問です(原文:循環参照とメモリリークに関して)

循環参照とメモリリークに関して

このQ&Aのポイント
  • 次のスクリプトはメモリリークを起こしているでしょうか。
  • 実際に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]);

質問者が選んだベストアンサー

  • ベストアンサー
回答No.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);

gorusura
質問者

お礼

様々なご意見を頂き、大変参考になります。 ありがとうございます。 /* 使用 */ の部分は、実際に自分が書くわけではないので、メモリリークパターンになる可能性があるのですが、それをどうにかしようと、/* 定義 */の部分でもがいていました。(ただし、それぞれの要素に、イベントをそれぞれ付けるという条件付き。) しかし、やはりそれは無理だと感じることが出来ました。 よって、他の問題もあり、今後は個々の要素にイベントを付けることは極力避けるよう、気をつけようと思います。

その他の回答 (1)

回答No.1

この手の質問は、苦手ですが恐れずに(漢字も含め)答えてみます。 間違いがあれば指摘が入るでしょうから。 >次のスクリプトはメモリリークを起こしているでしょうか。 まず、メモリーリークするのかは、ツールがあったりすのでそれで確認してください。 最近のブラウザでは、それは改善されているそうです。 「メモリーリークを起こしているのか」と「メモリーリーク・パターンなのか」とを 同じに考えないでください。 -- いわゆるクロージャーのなかで、グローバル変数に代入された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 で監視できるのだからそちらで書けっ!」 と私の偉人名言集に載っています。

gorusura
質問者

お礼

補足 /* 定義 */ (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]); すみません。 訂正します。 こちらが正しいコードです。

gorusura
質問者

補足

/* 定義 */ (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で振り分けていますが、新たな知識として、加えたいと思い、質問させて頂きました。 漢字での御回答ありがとうございます。

関連するQ&A

専門家に質問してみよう