• ベストアンサー

再帰で関数を呼び出すとforループがうまくいかなくなる

プログラミング初心者です。 かなり初歩的な理解の間違いをしているかもですが、その都度指摘していただけたら幸いです。 ふとHTMLのdocumentを構成している全ノードの情報(具体的にはinnerHTMLとノード名について)を表示させようと思い、次のようなコードを書きました。 function getElementsinfo(anode){ if ((anode.childNodes.length) == 0){ alert(anode.innerHTML); alert(anode.nodeName); alert("最下層ノードです"); }else{ nagasa = anode.childNodes.length; for(j=0;j<nagasa;j++){ alert(anode.childNodes[j].innerHTML); alert(anode.childNodes[j].nodeName); getElementsinfo(anode.childNodes[j]); } } return 0; } getElementsByinfo(document.body); 私の意図するところとしては、最下位のノード(子ノードが存在しないノード)までたどり着いた場合に、forループを利用してnextSiblingにあたるノードの情報を表示できるようにしたかったのですが、 どうも最下位のノードまでたどり着いたところで関数の実行が終了してしまうようなのです。 つまり、ノードが 1 |_____ | | | 11 12 13 |_______ |  |   | 111 112 113 のような構造をしている場合 1→11→111→112→113→12→121→122…のようにノードの情報を表示させたかったのですが 1→11→111まで表示してプログラムの実行が止まってしまっています。 この問題に対し貴重なご意見をいただきたく存じます。 現状の私の理解では解決策を思いつかず、投稿させていただきました。 質問のわかりにくい点については随時補足させていただきます。

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

  • ベストアンサー
  • nobuoka
  • ベストアンサー率69% (23/33)
回答No.2

グローバル変数と局所変数の違いはわかりますか? 以下のように、 var を使って変数を宣言した場合、関数内をスコープとする変数になります。 var variable = 100; 一方、関数内で var を使って宣言されていない変数を使用すると、自動的にグローバル変数となり、あらゆる場所から参照可能になります。 zabiora さんの例の場合、変数 nagasa と変数 j はグローバル変数となってしまっています。 そのため再帰的に呼び出した関数内での変数 nagasa と、元の関数内の変数 nagasa は同じ変数ということになり、再帰的に呼び出された関数内で変数 nagasa に値を代入してしまうと問題発生・・・、というわけです。 解決するには、変数 nagasa と変数 j を var をつけて宣言すればいいと思います。 変数名を一部変えちゃってますが、こんな感じに。 function getElementsinfo( aNode ){ if( aNode.childNodes.length == 0 ) { window.alert( aNode.nodeValue + "\n" + aNode.nodeName + "\n最下層ノードです"); } else { // 変数 length と変数 i を局所変数に var length = aNode.childNodes.length; for( var i = 0; i < length; i++ ) { window.alert(aNode.childNodes[i].nodeValue + "\n" + aNode.childNodes[i].nodeName); getElementsinfo( aNode.childNodes[i] ); } } return 0; } getElementsinfo(document.body); あと innerHTML プロパティは W3C DOM の勧告にはない独自仕様なので使わないほうが吉かと。 どうせループして全ノードをチェックするのですから nodeValue プロパティでいいのではないかと思います。 (そんなわけで上のサンプルでも nodeValue プロパティを使用しています。)

zabiora
質問者

お礼

非常にわかりやすい解説に感謝致します。 グローバル変数と局所変数ですか…。完全に失念しておりました。 初め一通り学んだときに知識として入れていても、 いざコードを書くときにちゃんと思い出せなければいけませんね。 innerHTMLは正式な仕様ではないのですか。 これからはちゃんとW3Cの文書を読む癖もつけていきたいです。 本当に回答ありがとうございました。

その他の回答 (2)

回答No.3

よけいなことかもしれないけど if ((anode.childNodes.length) == 0){ は、 if (! hasChildNodes()) { でよくない? あら~ともうざいので、 alert([anode.innerHTML,'\n',anode.nodeName]); とか、 けんさくするのなら function getNextElement (n) {  var e;  while (n) {   e = n.firstChild || n.nextSibling   if (! e) {    do {     if (! (n = n.parentNode)) return null;    } while (! (e = n.nextSibling))   }   n = e;   if (1 === n.nodeType) return n;  }  return null; } とか。 まったくよけいなことだけど ^^;

zabiora
質問者

お礼

回答ありがとうございます。 >よけいなことかもしれないけど いえいえ、全く余計では無いです。むしろ色々なやり方、より簡潔なやり方を教えていただいて感謝します。 私は本当に知識が乏しいので、このような指摘もありがたいです。

回答No.1

/* 変数は必ず宣言しよう */ /* 仕様未確認。 Minefield(Firefox 3.6a1pre)で動かす限り, 変数宣言がなかった結果,ここに宣言されたものとされるっぽい。 なので再帰を実行して戻ってきたとき,呼び出し前の変数の値は呼び出し先によって上書きされているものと思われる。 */ var nagasa; var j; function getElementsinfo(anode){ if ((anode.childNodes.length) == 0){ alert(anode.innerHTML); alert(anode.nodeName); alert("最下層ノードです"); }else{ nagasa = anode.childNodes.length; for(j=0;j<nagasa;j++){ alert(anode.childNodes[j].innerHTML); alert(anode.childNodes[j].nodeName); getElementsinfo(anode.childNodes[j]); } } return 0; } getElementsinfo(document.body); /* 関数名にByが入ってたので撤去 */ ================================= function getElementsinfo(anode){ if ((anode.childNodes.length) == 0){ alert(anode.innerHTML); alert(anode.nodeName); alert("最下層ノードです"); }else{ var nagasa = anode.childNodes.length; /* varをつけて宣言してみる */ for(var j=0;j<nagasa;j++){ /* varをつけて宣言してみる */ alert(anode.childNodes[j].innerHTML); alert(anode.childNodes[j].nodeName); getElementsinfo(anode.childNodes[j]); } } return 0; } getElementsinfo(document.body); /* 実際にはalertがいちいち表示されるのはうざかったので 改行を挟みながら文字列をつなげるようにして 結果の表示は最後だけにした。alertじゃなくても別に出力用の要素があってもいいよね */

zabiora
質問者

お礼

回答有難うございます。 変数の宣言というのはやはり明示的にするべきなのですね。 自分の理解の至らなさを痛感させられます…。 修正後のコードまでご丁寧に示していただき本当に感謝いたします。

関連するQ&A