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

この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]);
回答を見る
  • ベストアンサー

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

次のスクリプトはメモリリークを起こしているでしょうか。 function process(listener){ return function(evt){ listener.call(evt.target,evt); }; } function addEvent(element,type,listener,useCapture){ element.addEventListener(element,type,process(listener),useCapture); } var div=document.getElementsByTagName('div')[0]; //存在するものとして addEvent(div,'click',function(){;},false); //いかにも起こしそう div.parentNode.removeChild(div); //親も存在するものとして 工夫してみたものの、やはりメモリリークするんでしょうか。 実際に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

  • メモリリークに関して

    お世話になります。クロージャ使用によるメモリリークについて、 function doHoge(element) {  element.onClick = function() {   // do domething  } } でメモリリークが起こりますが、次の場合はどうなるでしょうか。 function doHoge() {  var element = document.getElementById("aa");  element.onClick = function() {   // do domething  } } あるサイトではdoHogeの引数のみに言及していましたが、この場合もクロージャはelementを参照するのでメモリリークが起こると思うのですが、 どうでしょうか。 よろしくお願いします。

  • DOMを使ったjavascript「続きを読む」(表示/非表示 切替え)初期値変更について

    以下は拾ってきたもので、 長い文章の途中に「続きを読む」などと入れて 内容を隠しておくのに使えるものだと思いますが、 設置してみたところ初期値で表示状態になっています。 これを非表示にするには どの値を変更すれば良いでしょうか。 javascriptの知識がないので、教えて下さい。 // イベントリスナーの追加 function addEvent(node, evt, func) { if (node.addEventListener) { node.addEventListener(evt, func, false); } else if (node.attachEvent) { node.attachEvent("on" + evt, func); } } // イベントがあった要素を得る function getTarget(e) { if (e.target) { return e.target; } else if (e.srcElement) { return e.srcElement; } } // イベントリスナーの割り当て function setHandler() { // 文書内のdiv要素を列挙する var divElements = document.getElementsByTagName('div'); for (var i = 0; i < divElements.length; i++) { // div要素の直下の階層にあるh4要素を得る var h4Element = divElements[i].getElementsByTagName('h4')[0]; // h4要素のclickイベントにイベントリスナーを割り当てる addEvent(h4Element, "click", showHide); } } // h4要素と同じ階層にあるp要素の表示/非表示を切り替える function showHide(e) { // クリックされたh4要素を得る var h4Element = getTarget(e); // h4要素と同じ階層にあるp要素を得る var parent = h4Element.parentNode; var pElement = parent.getElementsByTagName('p')[0]; // p要素の表示/非表示を切り替える pElement.style.display = (pElement.style.display == 'none') ? '' : 'none'; } // イベントリスナーを割り当てる addEvent(window, "load", setHandler);

  • jQuery の one 関数について

    jQuery の one 関数は便利だと思い、是非作ってみようと思いましたが、全然出来ません>< 一応以下のように作ってありますが、正しく動作しない上に、name を指定して、 Listener.data オブジェクトにname で保存をするようになっていて、同じnameを渡したら上書きされてしまいます。 どうしたら、jQueryのような、one関数を作れるでしょうか?;; var Listener={ addEvent:function(element,type,callback,useCapture){ if(element.addEventListener){ return element.addEventListener(type,callback,useCapture); } else if(element.attachEvent){ return element.attachEvent('on'+type,callback); } else{ return element['on'+type]=callback; } }, removeEvent:function(element,type,callback,useCapture){ if(element.removeEventListener){ element.removeEventListener(type,callback,useCapture); } else if(element.detachEvent){ element.detachEvent('on'+type,callback); } else{ return false; } return true; }, addOne:function(name,element,type,callback,useCapture){ Listener.addEvent(element,type,callback,useCapture); Listener.addEvent(element,type,function(){ Listener.removeEvent(element,type,); },useCapture); this.data[name]=new Function("Listener.removeEvent("+element+",'"+type+"',Listener.data['"+name+"'],"+useCapture+");+callback); this.addEvent(element,type,this.data[name],useCapture); }, data:{ } }; addEventとremoveEventは正常動作しています。 現在は、 Listener.addOne('name'/*指定しなくてもいいようにしたい*/,'document'/*オブジェクトを渡すと、elementがobjectになり、未定義エラーに*/,function(){alert('OK');},true); となっていますが、目指すは以下のような書き方です。 //仮関数定義 function test(str){ alert(str); } var str='test'; Listener.addOne(document,function(str){test(str);},true); です。 一応、jQueryのone関数を見てみましたが、jQueryは使ったことが無く、あまり分かりませんでした;; 御回答宜しくお願いしますm(_ _)m

  • event量産

    はじめまして! さっそくですが以下を <script type="text/javascript"> function $(e) { return document.getElementById(e); } function $$(n) { return $(n).childNodes[0].href; } function lh(u) { top.location.href = $$(u); } function addevent(node,evt,func){ if(node.addEventListener){ node.addEventListener(evt,func,false); } else if(node.attachEvent){ node.attachEvent("on"+evt,func); } } addevent( window,"load", function(){ //↓ここからが質問です addevent($("css_link"), "click", function(){ lh("css_link"); }); //↑これがうまくいったので var doo = "other_link"; addevent($(doo), "click", function(){ lh(doo); }); //↑次に向けてのテスト これもうまくいきました for(i=1;i<20;i++) { var names = "page"+i; addevent($(names), "click", function(){ lh(names); }); } //↑しかしこれがうまくいかない } ); </script> やっていることは<DIV>の中にある<A>のhref内容を読み取り、<DIV>と<A>の隙間をクリックしても同じURLにジャンプさせたいということです。 上記の"css_link"や"other_link"などの固定URLは羅列するつもりです。 これは問題なく動作します。 問題の ループさせている部分は"page1"~"page19"まで可変でPHPで書き出す部分です。 この部分の処理方法を教えてください。 もうひとつ質問です とりあえず19回ループさせていますが、ページ内に存在しないidを指定しても(実際はpage1,page2だけとか)現在はエラーにならないようですが、問題点などありましたら教えてください。 よろしくお願いします!

  • 指定したレイヤーをfixed表示にする

    var css={ //省略 doFixed:function(element,LayerX,LayerY){ if(element.style.position!='absolute'){ element.style.position='absolute'; } var LayerLeft=this.getLayerLeftonPage(element); //ページ上、レイヤーの左辺座標 var LayerTop=this.getLayerToponPage(element); //ページ上、レイヤーの上辺座標 this.setLayerPosition(element,LayerLeft,LayerTop); //対象,セットする対象の左辺座標,セットする対象の上辺座標 LayerX=LayerX ? LayerX : LayerLeft; //つまり、引数のLayerXは省略可能 LayerY=LayerY ? LayerY : LayerTop; //つまり、引数のLayerYは省略可能 addEvent(window,'scroll',function(){//イベントリスナです。 css.setLayerPosition(element,LayerX+css.getScrollLeft(),LayerY+css.getScrollTop()); /* ウィンドウ上でスクロールされたら、直ちにセット。 css.getScrollLeft……X方向のスクロール量 css.getScrollTop……Y方向のスクロール量 */ }); addEvent(document,'mousemove',function(evt){//ドラッグ要素にしたりしたときの対策 if(getEventElement(evt)===element){//event.targetのクロスブラウザ LayerX=css.getLayerLeftonPage(element); LayerY=css.getLayerToponPage(element); } }); }, //省略 }; このように、指定したレイヤーをfixed表示にする方法を思いついたのですが、どうしてもガタついてしまいます; 背景画像を固定するという方法も見かけますが、それは避けたいです>< どうにかして、出来ないでしょうか? http://kyukyoku.xrsp.net/cgi-bin/Test.cgi こちらがテストです。 御回答宜しくお願いしますm(_ _)m

  • 表示オブジェクトの参照

    下記のxTestから表示リストコンテナに追加された「bm」をdisposeするスクリプトを書こうと思って躓いています。 「bm」はローカル変数なので他の関数からアクセスできないと思いますので、getChildAt(0)のようにメインタイムラインから辿って参照できないかと考えています。 この方法をどなたか教えていただけないでしょうか。 また別の参照方法があればそちらでもOkです。 どうぞよろしくお願いいたします。 メインタイムライン → bm → bmd var bmdX:uint = 500; var bmdY:uint = 320; var bmX:uint = 0; var bmY:uint = 0; var loadImg = new Loader(); loadImg.load(new URLRequest("0.jpg")); loadImg.contentLoaderInfo.addEventListener(Event.COMPLETE,xLoader); function xLoader(evt:Event):void { var bmd:BitmapData = new BitmapData(bmdX,bmdY); bmd.draw(loadImg); var bm:Bitmap = new Bitmap(bmd); this.addChild(bm); bm.x = bmX; bm.y = bmY; } _btn.addEventListener(MouseEvent.CLICK,xTest); function xTest(evt:MouseEvent):void { //▼「bm」にここからアクセスしてdispose()したい! }

  • C# htmlの一部の文字取得

    C#のwebBrowserで表示されているtextを表示させたいです。 htmlが <div class="AAA"> BBB </div> となっており、BBBのtextを取得したい場合、 HtmlElementCollection elem = webBrowser0.Document.GetElementsByTagName("div");   foreach (HtmlElement element in elem)   {    if (element.GetAttribute("class") == "AAA")  {  str = element.InnerText;  } } と、しましたが、上手く取得出来ません。 ご教授頂ければ助かります。

  • jQueryで要素を取得した後の使い方について

    jQueryでグローバル変数に要素を代入した後にその変数を利用したいのですが記述方法がわかりません。 var element; // クリックしたinputの要素をelementに代入 $(function(){  $("input").click(function(){   element = $(this);  }); }); // elementを利用する function alertEx(){  alert($(element).val()); } alertEx関数を実行するとクリックしたinputの値を表示するようにしたいです。 alertEx関数でのelementの使い方が間違っていると思うのですが、どのように記述すればinputの値をalert表示できるのでしょうか? 知りたいのは、あくまでも要素をelementに代入した後の利用の仕方です。 表示方法とかではないです(上記コードは例です)。 どうぞよろしくお願い致します。

  • スタイルシートのレイヤーに乗せた画像を動的に表示する方法

    <div id="test" style="position=absolute; top=400px; left=200px; z-index=10;"><img src="gazou.gif"></div> 上記を動的に表示するために以下の方法を考えました var element = document.createElement('div'); element.id = "test"; element.style = "position=absolute; top=400px; left=200px; z-index=10;"; element.innerHTML = '<img src="gazou.gif">'; var objBody = document.getElementsByTagName("body").item(0); objBody.appendChild(element); しかしこれだと、element.style = "position=absolute; top=400px; left=200px; z-index=10;"; の部分がまずいらしく動作しません。 どのようにすればよいでしょうか。 よろしくお願いします。

  • JavaScriptでコンストラクタについて

    現在、JavaScriptを勉強中なのですが、 コンストラクタについて質問です。 下記のようなコードの場合、document.getElementByIdで取得したID 以外、使いまわしが出来ませんが、 コンストラクタを使って、1ページで複数使用出来るようにするには、 どのように記述すれば良いのでしょうか? //-----JavaScript----- function addEvent(elm,listener,fn){ try{ elm.addEventListener(listener,fn,false); }catch(e){ elm.attachEvent("on"+listener,fn); } } var c,y,m,d; addEvent(window,"load",function(){ c = document.getElementById("customer_birthday_c"); y = document.getElementById("customer_birthday_y"); m = document.getElementById("customer_birthday_m"); d = document.getElementById("customer_birthday_d"); if(c.options[0].selected == true){ y.disabled = true; m.disabled = true; d.disabled = true; } }); //-----JavaScript----- どなたかご教授ください。 宜しくお願いいたします。

専門家に質問してみよう