質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

87.61%

appendChildの挙動について教えて下さい。【JavaScript】

解決済

回答 7

投稿 編集

  • 評価
  • クリップ 1
  • VIEW 4,400

score -34

appendChildの挙動について教えて下さい。【JavaScript】

プログラミング初心者です。
documentオブジェクトのappendChildメソッドは、
「追加するノードが既にdocument内に存在するノードの参照であった場合、 もともとあった方を削除してから追加する」
そして
「document内に同じノードが2個以上存在できない」
そうですが、
appendChildおよびdocumentは何をもって、

『もともとdocument内に存在するノードA』 と 『新しく追加しようとしているノードA'』 が同じものである

と判断しているのかわかりません。

実際に困っていること

html+JavaScript+CSSで、以下のようなプログラムを作っているのですが、
挙動がうまくいきません:

〈要件定義〉
・黄色の背景のdiv領域(id="wrapper")に
    赤色の小さなdivの箱(class="hako")を
    一定時間毎(2秒)に無限増殖させる。
・スタートボタンを押すと、赤いdiv箱が出現して増殖を開始し、
   放っておけば2秒ごとに領域内にdiv箱が一つずつ生成される。
〈問題点〉
・ボタンを押しても、div箱は、見た感じは初めの一個しか生成されない。
・開発者ツールを見ると「一応、div箱は2秒ごとに生成されてはいる」と思われる。
・原因はおそらく、上記のappendChildの仕様により、生成する毎に元あった箱が削除されているのではないか、と思われる

以下が、問題のコードになります。

 コード

OSはWindows10、ブラウザはchromeです。
【html】

<!--<!DOCTYPE html>
DOC宣言を書くと、互換モードから標準モードになり、
styleに不具合が生じる!これから書かない!
-->
<html lang="ja" dir="ltr">
  <head>
    <meta charset="utf-8">
    <link rel="stylesheet" type="text/css" href="zoushoku.css">
    <script src="zoushoku.js" charset="utf-8"></script>
    <title>増殖(オブジェクト指向的)</title>
  </head>
  <h1>増殖(オブジェクト指向的)</h1>
  <body>
    下のボタンを押すとボックスが生まれます。 <br>
    一定時間ごとに増殖します。 <br>
    <button id="startBtn" name="startBtn" type="button">start</button><br>
    <div id="wrapper" class="clearfix"></div>
  </body>
</html>


【css】

@charset "UTF-8";
/*.clearfix:after{
  content:"";
  display:block;
  clear:both;
}
*/
#wrapper{
  width:500;
  height:500;
  background-color: yellow;
}

.hako{
  width: 30;
  height: 30;
  background-color: red;
}


【JavaScript】

window.onload = function() {

  /*増殖するhakoの設計図*/
    var hako = document.createElement("div");//新しいdivエレメントを生成
    hako.className = "hako";//class名はhako

    /*hakoが住んでいるdivエレメント領域をwrapperとする。*/
    var wrapper = document.getElementById("wrapper");

    /*hakoの増殖関数*/
    function zoushoku(){
      setInterval(function(){wrapper.appendChild(hako)},2000);
    };//こうして関数宣言によってラッピングしてやれば、setIntervalは勝手に実行しない。
    var start = document.getElementById("startBtn");//増殖スタート
    start.addEventListener("click", zoushoku);

};

原因の検討はついたものの

私の仮説が正しければ、

「appendChildによって、毎回(2秒ごとに)、ノードの追加の前に、ノードの削除が行われている」

つまり、

「同じファイル名のファイルを作成するとき、もともとあるファイルを削除してから作成する」

みたいなことがされている、ということなので、
追加するdiv箱のプロパティを調整することが必要なのではないかと思いました。

しかし、appendChildが何をもって、
「もともとdocument内にあるdiv箱と、これから追加しようとしているdiv箱が同じ」
と判断しているのか、調べても分からないので、対処の仕方がわかりません。

「classNameが重複しているのがいけないのか」と一瞬思いましたが、
class名はidとは違って個体識別子ではないので、そこを疑うのは不自然だと思いました。

あと考えられることといったら、
「JavaScript、HTMLの言語処理系の仕様により、
HTMLドキュメント内のDOMと、JavaScriptのスクリプト内の変数が、
同期されてしまっている」
というところでしょうか?
そこらへんの言語処理系の仕組みについてはあまり勉強が進んでいないので、対処の仕方がわかりません。

どう思われますか?ご回答宜しくお願いします。

追記

ベストアンサーにとても迷いましたが、今回は、
問題の核心的な部分を理解する糸口を最初に示して頂いたmaisumakun様をベストアンサーにさせて頂きたいと思います。
他の皆さまも、とても参考になりました!ご回答ありがとうございました!

最後に、修正したjsのコードを這っておきます。
setInterval毎にcreateElementを発動するように、createElementのところもzoushoku(){}の内部に包んでやりました。

window.onload = function() {


    /*hakoが住んでいるdivエレメント領域をwrapperとする。*/
    var wrapper = document.getElementById("wrapper");
    /*hakoの増殖関数*/
    function zoushoku(){
              /*増殖するhakoの設計図*/
              /*createElementは同じ宣言から生まれたオブジェクトは同じものとみなされ、
*             appendCHild標準機能である「同一オブジェクトの追加時には元のものを削除」
が発動してしまうため、この関数の内部に記述して、
次から次へとcreateElementを宣言しなおさなければならない。
そうすることで、「別々のcreateElement」から「別々のオブジェクト」がちゃんと生成される*/
          var hako = document.createElement("div");
          hako.className = "hako";//class名はhako
          wrapper.appendChild(hako);//子ノードリストにノードを追加
    };//こうして関数宣言によってラッピングしてやれば、setIntervalは勝手に実行しない。
    var start = document.getElementById("startBtn");//増殖スタート
    start.addEventListener("click", function(){
      setInterval(zoushoku,2000)
    });

};
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • m.ts10806

    2019/03/31 19:37

    これまで何度も何度も指摘されてるじゃないですか。
    仕様をそのまま受け取ってコード書けって。
    それができないならプログラミングやめた方がいいです。仕様にそって動くだけのもの、それ以上の理解は邪魔でしかない。ロジックだけ考えること。
    で、何になりたいんでしたっけ?目標は言語を作る人ですか?既存の言語でなにかを作る人ですか?趣味ですか?仕事ですか?
    仮に仕事だとしたら無理でしょうね。

    キャンセル

  • WeilSpinor

    2019/03/31 19:49 編集

    もちろん、読むだけで実際の実装の仕方やコツ、注意点、その他見落としていることに気付けるなら良いですが、いまの私には経験が足りないうえ、mts様ほどの太平洋の如く広大なワーキングメモリを持っているわけでも、素晴らしい理解力があるわけでもないと思います。

    だからといって、
    その都度その都度、その人にとってちょうど良くわかりやすい説明をしてくれている記事が都合よく存在するわけではないですし、

    それならば、仕様書を読む能力をあげるしかなく、
    仕様書を読む能力をあげるためには、
    原理を知るのが近道である。

    凡人たるわたくしがプログラミングを理解するためには、身の程をわきまえつつ、
    こういった不断の工夫の日々が必要だと、思っただけです。

    ご返信は結構です。

    キャンセル

  • m.ts10806

    2019/03/31 19:57

    今まで一通り無視してきた方が「ご返信は結構です」とは横暴ですね。
    話を話の通り受け取らない人はどんなことでも理解が進むようにはなりませんよ。
    本来そういう指摘をする場ではないのは重々承知ですが、あなたについては仕様書や記事が問題ではないです。あなた自身の性格です。
    車や自転車を運転するのにその原理って必要な知識ですか?洗濯機を使うのに原理は必要な知識ですか?
    説明書にどこまで書いてありますか?
    車や洗濯機を作るなら必要でしょうね。使うなら全く不要です。

    キャンセル

回答 7

checkベストアンサー

+5

hakoを生成している(document.createElementを呼んでいる)のは、window.onload1回のみです。Timerが動作しても、同じインスタンスをそのまま使い回すこととなります。


本題ではありませんが、いまどきあえて互換モードで書くべきではありません。素直に<!DOCTYPE html>で標準モードで書きましょう。jQueryすら、互換モードでの動作を保証していません。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/03/31 22:34

    > イメージとしては、

    全然違います。たとえば、「{} == {}」がfalseとなるように、「動的なラベル付け機能」なんてなくても、オブジェクトの同一性判定はきちんと作りつけてあります。

    キャンセル

  • 2019/03/31 22:35

    > もしかして(後略)

    いえ、そんなところに踏み込まないでください。

    キャンセル

  • 2019/03/31 22:45

    なるほど…オブジェクトって難しい(というより奥深い?)んですね…。

    とりあえず、ブラックボックスには目をつむって、
    「別々のメソッド宣言から生み出されるオブジェクト(インスタンス)は別々のモノと判定される」
    と覚えておきます。(原理を知りたければ、関数やオブジェクト、トークンの意味の理解とか、ですかね…)

    ご回答ありがとうございます。

    キャンセル

+3

appendChildが何をもって、
「もともとdocument内にあるdiv箱と、これから追加しようとしているdiv箱が同じ」
と判断しているのか、

createElementで作った1つのエレメントは、どこの変数に代入しようが、どこにappendChildしようがずっと同じ物です。
別々のcreateElementで作った別々のエレメントは別の物です。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/03/31 21:45

    ご回答ありがとうございます。

    そのご説明だと、
    createElementで作ったエレメントには、
    それを宣言したJavaScript上と生成先のHTML上において、
    同じものであることを規定する、なんらかの「リレーション」が定義される、
    という機能があるのでしょうか?

    キャンセル

  • 2019/03/31 21:49

    そういうことではありません。2つのものがあり、その間に「同じ」という関係があるのではなく、最初から1つなのです。

    「生成先のHTML」というより「生成先のDOM」ですね。

    キャンセル

  • 2019/03/31 22:19 編集

    そうですか…。

    上のコードでは、
    createElementで作ったdiv要素を格納した変数hakoを、zoushoku関数内部のsetIntervalの引数にセットして、clickで引火してドキュメント内のDOMに射出しているわけですが、
    2回目以降は、一つ前に射出したものの「完全なコピー」を射出しているに過ぎない、ということですかね…。
    …多分、わかりました。
    一連の動作・判断の主体はあくまで「JavaScript」ということを考えれば、合点がいきます。

    ところで、
    「別々のcreatElementで作ったノードたちは別モノ」
    ということは、イメージとしては、
    creatElementというメソッドにはカウンタのようなものが定義されていて、
    実行毎にカウントが更新され、
    生成されたノードにはそのカウントデータを渡しており、
    そのカウントをラベルにノード同士の違いを判断している、
    ような感じでしょうか?

    マニュアルhttps://developer.mozilla.org/ja/docs/Web/API/Document/createElement
    にはそのようなことは書いていないので想像ですが、
    いずれにしても、何かしら似たような動的なラベル付け機能がないと、つじつまが合わないきがします。

    キャンセル

+3

Node.prototype.appendChild

仕様を読みましょう。

Re: WeilSpinor さん

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/04/02 01:25

    ご回答ありがとうございます。参考にさせてきた抱きます

    キャンセル

+2

<button id="startBtn">startBtn</button>
<button id="stopBtn">stopBtn</button>
<button id="clearBtn">clearBtn</button>
<div id="wrapper"></div>
document.addEventListener( 'DOMContentLoaded' , e=> {
  let intervalID;
  let wrapper = document.getElementById( 'wrapper' );
  document.getElementById( 'startBtn' ).addEventListener( 'click' , e=> {
    intervalID = setInterval( e=>{
      let hako = document.createElement( 'div' );
      hako.className = 'hako';
      hako.innerHTML = 'hako';
      wrapper.appendChild( hako )
    }, 1000 );
  }, false );
  document.getElementById( 'stopBtn' ).addEventListener( 'click' , e=> {
    clearInterval( intervalID );
  }, false );
  document.getElementById( 'clearBtn' ).addEventListener( 'click' , e=> {
    clearInterval( intervalID );
    wrapper.innerHTML = '';
  }, false );
}, false );

動くサンプル:https://jsfiddle.net/L6zfejxy/

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/04/02 01:25

    ご回答ありがとうございます。参考にさっせえていkただ来ます

    キャンセル

  • 2019/04/02 01:27

    アロー関数など、普段使わない記法が習えて助かります。
    じっくり勉強させていただきます

    キャンセル

+1

DOMはツリー構造なので、ノードは複数の親要素を持てないのです。
ですから、ある要素がappendChildメソッドにより別の親要素の子要素となった場合、前の親要素との親子関係は保持できないです。

なんか分かりにくいかな、と思って追記を書いている間に解決しちゃった件
{
//配列で試す。

//子要素
  const c1 = ['c1'];
  const c2 = ['c2'];
//親要素
  const p1 = [c1];
  const p2 = [c2];

//親要素の中身はこうなってる。
  p1;
/*
[['c1']]
*/
  p2;
/*
[['c2']]
*/

//親要素 p1 に、子要素 c2 を追加する。
  p1.push(c2);
//親要素の中身はこうなる。
  p1;
/*
[['c1'],['c2']]
*/
  p2;
/*
[['c2']]
*/

/*
一見、c2が増えたように見えるけど、実は増えていない。
p1[1] と p2[0] は同じ c2 を見ている。
*/

//子要素 c2 を書き換える。
  c2[1] = "c2の2"
//親要素の中身はこうなる。
  p1;
/*
[["c1"],["c2","c2の2"]]
*/
  p2;
/*
[["c2","c2の2"]]
*/

/*
上記のとおり、p1[1] と p2[0] は同じ c2 を見ている。
ただし、配列はツリー構造ではないから、親を複数持てるので、こうなる。
*/
}

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/04/02 01:48

    このあと、簡単なツリー構造を作って、appendChild がツリー構造を正しく保てるのは「子が親への参照を持っているから」を示す予定でしたが、また次の機会にでも。

    キャンセル

  • 2019/04/02 01:57

    はい。ありがとうございました。

    キャンセル

  • 2019/04/02 02:00

    そういえば私、ちょうど10年前に、あなたと同じことでつまづいて、ブログに書いていましたよ。
    http://realtor-readyabooks.hatenablog.com/entry/20090212/1234438206
    いやあ、黒歴史だなあ……

    キャンセル

0

たとえ好きな質問者様だと記憶するので、比喩で回答します。

  • やりたいこと(の比喩)
    黄色のカーペットの上にたち、(一穴)パンチに、赤い色紙をセットして、パンチを連打して、カーペットのうえに、赤い●をたくさんちりばめること

  • コードとの照合
    黄色のカーペット=wrapper
    パンチを打つ行為=createElement()の実行
    赤=classname
    パンチのカタチ="div"
    繰り返すという行為=setInterval()

  • やっていること
    黄色いカーペットのうえに、あらかじめ一穴パンチを一回押して作った●をひろってはなげ、ひろってはなげ、している

  • やろうとしていることとやっていることの乖離
    ご自身がvar hakoと名付けているように、hakoは「四角く切り出された色紙」です。■です。決して、「パンチで新たな■を切り出そうとする行い」ではないです。
    ばらまくかかりの人(setInterval)に、「新しい■」ではなく、最初に一回だけ切り出した■(hako)を(引数として、まさに)渡したうえで、それを、カーペット(wrapper)に(.)付けて(appendChild)くれといって、無限に増えないじゃないかと言われても、ばらまくかかりの人は、「いや、1個しかもらってませんし」という状態です。
    kei344様の回答にあるように(勝手に引用してすみません)、ばらまくかかりの人には、材料と材料を使った新しい断片の切り出し方も教えてあげる必要があります。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/04/02 01:24

    ご回答ありがとうございます。

    わぁ!とても分かりやすいたとえですね!
    createElementで■を調達して、
    appendChildでパンチを打つ。
    setIntervalは繰り返すという動作で…さすがです。

    キャンセル

-4

maisumakun様があげられた、「オブジェクトの同一性判定」というキーワードをたどって、
JavaScript公式リファレンスにおいて「オブジェクトの取り扱い」を勉強する中で、
今回の一連の疑問を解決する糸口をつかむことができたので、報告いたします。
以下のリンクにて、
https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Working_with_Objects
「オブジェクトの比較」という項目を読む限り、
やはり私の予想の通り、

「インスタンスの生成プロセス」がカギだったみたいです。

生成のプロセス自体はブラックボックスですが、
どうやら内部では、
定義されたクラス(今回はclass div要素)のインスタンスを生成するときには、
インスタンスの生成宣言の度に、そのインスタンスにラベル付けのようなことが行われていて、
それをもってそのスレッド内の「クラスが同じインスタンス同士」の区別を行っていると思われます。

とりあえず、スッキリしてよかったです。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/04/02 10:54

    > 「元あるものを消してから再登録する」

    そうですね。
    元の子ノードリストから消して、新しい親ノードの子ノードリストに再登録する。
    (でも、ノード自体は消さない)
    という理解でいいんじゃないでしょうか。

    その理解があれば、.removeChild() メソッドを実行しても要素が消えないことも、理解できると思います。

    キャンセル

  • 2019/04/02 11:03 編集

    今思いついた例えですが、養子縁組に似ていると思いました。

    生まれて(createElement)間もなくスラム街に捨てられた子供(div)は、親が不明で、戸籍上どの家庭にも属していない天涯孤独の身。
    孤児院で生活しているとある日、ある夫婦(wrapper)が養子縁組を申し出てきた。
    divは無事wrapper家引き取られ、wrapperの旦那はその日に役所で戸籍登録した。(appendChild)

    しかし後日、wrapper奥さんは、「まだdivの戸籍登録が済んでいない」と勘違いし、役所に行って登録作業をしたが、照会すると既に登録は済んでいたので、登録は無効になった。

    どうでもいい話なので、返信は結構です。

    キャンセル

  • 2019/04/02 11:12

    日本の法律だと、「普通養子縁組」は実親との親子関係が切れませんからツリー構造ではないですね(やめろ)

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 87.61%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る