全部言えたらDOMマスター！　HTML+DOMでノードを挿入する方法大全

DOMはHTMLの木構造(あとXMLも）やイベント等をプログラムから操作するためのAPIです。DOMの実装者として有力なのがウェブブラウザであり、ブラウザが提供するDOMのAPIを用いてJavaScriptプログラムからウェブページを制御するというのがDOMの極めてメジャーな使い道です。

近年知名度を増してきたReactやVueなどのライブラリも、もちろんこのDOMを用いて実装されています（Reactだと`react-dom`というあからさまな名前のパッケージがDOMを扱う部分を担当しています）。

この記事では、DOMで提供されるAPIを用いて木構造に**ノードを挿入**する方法**全38種類**を列挙します。

## ノードの挿入とは

DOMにおいて基本的な操作のひとつが**ノードの挿入**です。ノードというのは木構造の最小構成単位であり、**要素ノード**や**テキストノード**などの種類があります。例えば`<u>は</u>`というのは`u`要素を表すひとつの要素ノードであり（ここでは書きやすさのためHTMLタグで表現していますが、実際はタグの文字列ではなく木構造中のDOMオブジェクトのことをノードと呼びます）、`は`というテキストノードを子として持っています。

例えば以下のdiv要素もひとつのノードであり、子ノードとして`<b>こん</b>`と`<i>にち</i>`の2つを持っています（改行文字のことはここでは考えません）。

```html
<div>
  <b>こん</b>
  <i>にち</i>
</div>
```

では、このdiv要素の末尾に`<u>は</u>`というノードを子ノードとして追加してみましょう。すると以下のようになります。

```html
<div>
  <b>こん</b>
  <i>にち</i>
  <u>は</u>
</div>
```


このとき`<u>は</u>`がdiv要素の木構造に追加（挿入）されています。典型的には、Webページの木構造にノードを追加することでそのノードをページに表示することができますから、DOM操作においてノードの挿入はたいへん重要です。

なお、挿入という言葉に惑わされるかもしれませんが、木構造にノードが追加されていればそれは挿入です。中間的な場所でなくとも、木構造の一番最初や最後に追加するものも挿入と呼びます。では本題に入りましょう。

この記事では、このような操作を行う方法を可能な限り列挙します。その数は記事冒頭で述べた通り**38**種類（DOM仕様から14種類、HTML仕様から21種類、その他3種類）です。

DOM力に自信がある方は、ぜひご自身で考えてみてください。**38**種類全て思いついたらあなたはきっとDOMマスターです。

## レギュレーション

上記の**38**種類というのは以下のレギュレーションに従って数えたものです。

- **HTMLおよびDOMの仕様によって定義されたものが対象です。**
    - すなわち、jQueryやReactなどのライブラリは今回の記事とは関係ありません。
    - 仕様とは、WHATWGにより策定されているHTML Living Standard及びDOM Living Standardを指します。仕様がアップデートされて記事の内容が古くなってしまうことも考えられますが、そのときはぜひご指摘ください。
    - また、これら2つの仕様がdependenciesとして指定している諸仕様についても含みます。
    - これらの仕様でobsolete featuresと指定されているものは対象外とします。
    - DOMを参照するその他の周辺仕様（W3Cが策定しているJavaScript APIとか）が子ノードを追加できる可能性もありますが、主に筆者が追いきれていないという理由でこの記事では対象外とします。
- **ノードを挿入する能力を持つ関数（メソッド）ひとつにつき1種類と数えます。**
    - あるメソッドがオプションによって異なる挿入結果になったり挿入されなかったりするようなことも考えられますが、同じメソッドを使っているならばそれは1種類と数えます。
    - より厳密には、DOMやHTMLの仕様書において関数が呼び出された際の挙動は基本的に*the `someMethod()` method(, when invoked, ) must ～*というような文によって定義されます。この定義文1つを1種類と数えるものとします。
    - 関数呼び出し以外の操作の結果としてノードが挿入されることがあるかもしれませんが、その場合の数え方も上記に準じるものとし、その際もその操作によって起こることを定義する文ひとつを1種類と数えます。
- **ノードを挿入できる状況に制限があるものも数に含めます。**
    - 「特定の種類のノードしか挿入できない」や「特定の位置にしか挿入できない」といった制限を持つ方法が存在するかもしれませんが、こういったものも1種類と数えます。何らかの前提条件のもとでノードを木構造に挿入できるならばカウント対象となります。
    - 「確かにノードを挿入できるがそれ以外の副作用が発生する」（別のノードが消えるとか）という方法もカウント対象です。
- **ノードを新規作成して同時に追加するものも数に含めます。**
    - ものによっては「ノードを新規に作成してそれを即座に木構造に追加する」という挙動をするものがあるかもしれません。この場合も「ノードを挿入する」という目的は達成しているので数に含めます。
- **木構造ごと生成されるものは数えません。**
    - 例えば「子ノードを最初から持った親ノード」を新規作成する方法があり、新規作成した親ノードに新規作成した子ノード追加したとも言えるかもしれませんが、それは**数に数えません**。あくまで既存の木構造に（既存または新規の）子ノードを追加するものをカウント対象とします。
- **`document.write`は数えないでください。**
    - あれはパースされる前の文字列を追加するものなのでノードを追加した扱いではないことにします。もちろん`document.writeln`もだめです。

では、以下ではいよいよノードを挿入する方法を列挙します。皆さんは**38**種類全部言うことができましたか？

## ノードを挿入する方法一覧

### DOM編

DOM Living Standardで定義されているものをまずは列挙します。上記の通りDOM編は**14**種類です。

#### `Node#appendChild`

DOMでノードを挿入と聞いて一番にこれを思いつく方も多いでしょう。`appendChild`が`Node`が持つメソッドであり、与えられたノードを子ノードして親ノードの末尾に追加します。

同時に追加できる要素はひとつだけあり、追加できる場所は末尾だけです。扱えるノードの種類という観点では最も汎用的なメソッドのひとつであり、親ノードは`Document`, `DocumentFragment`, そして`Element`の3種類、子ノードは `DocumentFragment`, `DocumentType`, `Element`, `Text`, `ProcessingInstruction`, `Comment`の6種類に対応しています。

```js:appendChildのサンプル
const parent = document.createElement("div");
const child = document.createElement("span");

parent.appendChild(child);

console.log(parent); // <div><span></span></div>
```

#### `Node#insertBefore`

`insertBefore`は子ノードを挿入する位置を指定できる、`appendChild`の強化版です。第2引数に既存の子ノードを指定することにより、「そのノードの直前」という形で挿入位置を指定できます。

```js:insertBeforeのサンプル
// parentの先頭にchildを追加する
parent.insertBefore(child, parent.firstChild);
```

#### `Node#replaceChild`

`replaceChild`は既存の子ノードを別のノードで入れ替えるメソッドです。これも含めるのかとお思いかもしれませんが、既存の子ノードを取り除くという副作用がありつつも新しいノードを子ノードにする機能を持っているためこれもカウント対象です。

```js:replaceChildのサンプル
// parentの先頭子ノードをchildで置き換える（parent.firstChildが無い場合はエラー）
parent.replaceChild(child, parent.firstChild);
```

#### `Node#textContent`

これはメソッドではなく文字列プロパティです。`textContent`の値を読んだときはそのノードに含まれる全文字列をつなげた文字列が得られますが、`textContent`に新しい文字列を代入したときは、ノードの子がその文字列を表すテキストノード一つで置き換えられます。これもテキストノードを追加できたことになります。

```js:textContentのサンプル
const div = document.createElement("div");

div.textContent = "hi";

console.log(div); // <div>hi</div>  ←divの子にテキストノードが追加された！
```

#### `ParentNode#append`

これはいわゆるDOM 4で新しく追加されたメソッドなので、最近追っていない方は聞き覚えが無いかもしれません。これは`appendChild`と同様に好きな子ノードを末尾に追加できるメソッドですが、`appendChild`に比べて2つの点で強化されています。

一つは引数を何個でも取れる点で、与えたノードたちがその順番で全て追加されます。

もう一つはノードだけでなく文字列を引数に与えることができる点で、文字列は自動的にテキストノードに変換できます。`appendChild`でテキストノードを追加したい場合は`document.createTextNode`でテキストノードを生成する必要があったのに比べてだいぶ便利ですね。

```js:appendのサンプル
const parent = document.createElement("div");

const child1 = document.createElement("b");
const child2 = "hi"
const child3 = document.createElement("i");

parent.append(child1, child2, child3);

console.log(parent); // <div><b></b>hi<i></i></div>
```

#### `ParentNode#prepend`

`prepend`も同様にDOM4で追加されたメソッドで、要素の末尾ではなく先頭に追加します。引数に渡した順番は保たれます（逆順になったりはしません）。

```js:appendのサンプル
const parent = document.createElement("div");

const child1 = document.createElement("b");
const child2 = "hi"
const child3 = document.createElement("i");

parent.append(child1);
parent.prepend(child2, child3);

console.log(parent); // <div>hi<i></i><b></b></div>
```

#### `ChildNode#before`

これまたDOM4で追加されたメソッドで（これを含めてあと3個くらい続きます）、自身の**兄弟**を追加できるという特徴を持ちます。この`before`は、自身の**直前**に兄弟ノードを追加します。

兄弟を追加ということですが、親から見れば新たな子ノードが追加されたことになりますね。複数の引数を与えることができるのは`append`や`prepend`と同じです。

なお、兄弟という概念は親がいなければ成立しませんので、親の存在しないノードに対してこのメソッドを呼び出した場合はエラーとなります。

```js:beforeのサンプル
const parent = document.createElement("div");
const child1 = document.createElement("b");

parent.append(child1);

const child2 = document.createElement("i");

child1.before(child2, "hi");

console.log(parent); // <div><i></i>hi<b></b></div>
```

#### `ChildNode#after`

お察しとは思いますが、これは自身の直後に兄弟ノードを追加するメソッドです。

```js:afterのサンプル
const parent = document.createElement("div");
const child1 = document.createElement("b");

parent.append(child1);

const child2 = document.createElement("i");

child1.after(child2, "hi");

console.log(parent); // <div><b></b><i></i>hi</div>
```

#### `ChildNode#replaceWith`

`replaceWith`は、自分自身を引数で渡された別のノードで置き換えます。このメソッドも`before`や`after`と同様、親ノードが存在しないと呼び出せません。

```js:replaceWithのサンプル
const parent = document.createElement("div");
const child1 = document.createElement("b");

parent.append(child1);

const child2 = document.createElement("i");

child1.replaceWith(child2, "hi");

console.log(parent); // <div><i></i>hi</div>
```

#### `Element#insertAdjacentElement`

要素を指定した位置に追加するメソッドです。位置は文字列で指定し、`"beforebegin"`, `"afterend"`, `"afterbegin"`, `"beforeend"` の4種類があります。意味はそれぞれ前の兄弟ノード、 後の兄弟ノード、最初の子要素、最後の子要素です。

やや古いメソッドであり、後方互換性のために残されています。そのため、いま使う機会はあまり無いかもしれません（ここまでで挙げた`before`, `after`, `prepend`, `append`の4つで事足りますしね）。

```js:insertAdjacentElementのサンプル
const parent = document.createElement("div");
const child1 = document.createElement("b");

parent.append(child1);

const child2 = document.createElement("i");

child1.insertAdjacentElement("afterend", child2);

console.log(parent); // <div><b></b><i></i></div>
```

#### `Element#insertAdjacentText`

上記の`insertAdjacentElement`と似たようなメソッドですが、引数として要素を受け取る代わりに文字列を受け取り、それを中身とするテキストノードを指定の位置に追加します。これも`insertAdjacentElement`と同様、今から使う理由はあまり無いでしょう。

```js:insertAdjacentElementのサンプル
const parent = document.createElement("div");
const child1 = document.createElement("b");

parent.append(child1);

child1.insertAdjacentText("afterbegin", "hi");

console.log(parent); // <div><b>hi</b></div>
```

ここまで読んだ方は「え？　HTMLを構文解析してくれる`insertAdjacentHTML`の話は？」と思ったかもしれませんが、実はそれは[DOM Parsing and Serialization](https://w3c.github.io/DOM-Parsing/)という別の仕様の管轄となっているため紹介を後回しにします。

#### `Text#splitText`

これはちょっと難易度が高いです。知っていたらDOM通ですね。

`splitText`はテキストノードを指定した位置で分割して2つに分けるメソッドです。親ノードがある状態でこのメソッドを使った場合は1つだった子ノードが2つに分割されたことになり、子ノードの追加が発生しています。

```js:splitTextのサンプル
const parent = document.createElement("div");
// テキストノードを1つ追加
parent.append("Hello, world!");

// 6文字目でテキストノードを分割
parent.firstChild.splitText(6);
// 前半のあとに別のテキストノードを追加
parent.firstChild.after(" my");

console.log(parent); // <div>Hello, my world!</div> ← "Hello," " my" " world!" の3つのテキストノードを子に持つ
```

#### `Range#insertNode`

`Range`はDOMが提供するオブジェクトのひとつで、木構造上の範囲を表したり操作したりできる機能を持ちます。あまり馴染みがないかもしれませんが、ユーザーがマウス等でテキストを選択した範囲を取得するときなどは`Range`のお世話になっています。

`insertNode`は、`Range`の開始点に指定したノードを挿入できるメソッドです。

```js:insertNodeのサンプル
const parent = document.createElement("div");

parent.append("Hello, world!");

// rangeを新規作成
const range = document.createRange();
// 開始点をparentの最初の子要素（"Hello, world!" テキストノード）の6番地に設定
range.setStart(parent.firstChild, 6);
// 開始点に<b></b>を挿入
range.insertNode(document.createElement("b"));

console.log(parent); // <div>Hello,<b></b> world!</div>
```

#### `Range#surroundContents`

こちらも`Range`のメソッドで、`Range`の範囲を与えられたノードで囲むという動作をします。`Range`が表す範囲によってはノードで囲むという操作が不可能な場合がありますが、その場合はエラーとなります。

```js:surroundContentsのサンプル
const parent = document.createElement("div");

parent.append("Hello, world!");

// rangeを新規作成
const range = document.createRange();
// rangeがparentの中身全体を囲むように設定
range.selectNodeContents(parent);
// rangeが表す範囲をb要素で囲む
range.surroundContents(document.createElement("b"));

console.log(parent); // <div><b>Hello, world!</b></div>
```

以上でDOM編は終了です。全**14**種類のうち何個分かりましたか？

### HTML 編

HTML編はさらに魔境です。筆者の見逃しも普通にあるような気がするのでぜひ筆者よりもHTML仕様書を読み込んでばしばし指摘しましょう。

HTML編は全部で**21**個です。

#### `document.title`

`document.title`は文字列プロパティで、現在のページタイトル（`title`要素の中身）を取得したり変更したりできます。

HTML文書において`title`要素が存在しない状態で`document.title`に文字列が代入された場合、新しいtitle要素が生成されたhead要素に挿入されるのです。

```js:document.titleの例
// 一旦title要素を削除
document.querySelector("head > title").remove()

console.log(document.querySelector("head > title")); // null

// document.titleに代入
document.title = "hi";

console.log(document.querySelector("head > title")); // <title>hi</title>
```

#### `document.body`

`document.body`はbody要素を取得できるプロパティですが、実は新しいbody要素を代入できます。これは`html`要素の子となりますから、body要素を木構造に挿入したといえます。

ちなみに、`document.body`よりも後に作られた`document.head`は代入不可の仕様になっています。

#### `HTMLElement#innerText`

HTML要素の`innerText`プロパティに文字列を代入すると、要素の中身が与えられた文字列で書き換えられます。ということは、新しいテキストノードが作られて挿入されるということですね。

ちなみに、`textContent`との違いは、親切にも改行がbr要素に変換されるという点です。

```js:innerTextの例
const parent = document.createElement("div");
parent.innerText = "foo\nbar";

console.log(parent); // <div>foo<br>bar</div>
```

#### `HTMLTitleElement#text`

`title`要素を表すノードである`HTMLTitleElement`は、`text`プロパティを通じて中身のテキストを得ることができます。また、テキストノードの書き換えも行えます。

中身が空などの状態では、`text`に代入することによって新しいテキストノードをtitle要素内に挿入できます。このタイプは今後も何個か出てくるので覚悟しておきましょう。

#### `HTMLAnchorElement#text`

`a`要素を表す`HTMLAnchorElement`も、`text`プロパティで中身のテキストを書き換えられます。

#### `HTMLTableElement#createCaption`, `HTMLTableElement#createTHead`, `HTMLTableElement#createTFoot`, `HTMLTableElement#createTBody`

似ているので4種類まとめて紹介します。これらは`table`要素を表す`HTMLTableElement`に存在するメソッドです。読んで字のごとく、これらはtable要素内にそれぞれcaption要素、thead要素、tfoot要素、tbody要素を作って返します。tbody要素以外は一つしかtable要素内に存在できないため、すでに存在する場合は新しく作るのではなく既存のものが返されます。

#### `HTMLTableElement#insertRow`

これは`table`要素内に`tr`要素を作成して挿入するメソッドです。引数の数値で挿入位置を指定できるのが特徴です。

必要に応じて`tr`要素だけでなく`tbody`要素も一緒に作られることがあります。

#### `HTMLTableSectionElement#insertRow`

`table`要素だけでなく`tbody`・`thead`・`tfoot`要素も`insertRow`を持ちます。なお、これらの要素は定義上では`HTMLTableSectionElement`という共通のインターフェースを持っていますので、レギュレーションに従ってこの`insertRow`は1種類と数えます。

#### `HTMLTableRowElement#insertCell`

`tr`要素を表す`HTMLTableRowElement`は`insertCell`メソッドを持っており、指定した位置に新規作成した`td`要素を挿入できます。

#### `HTMLOptionsCollection#add`

`select`要素等の子`option`要素の一覧を表す`HTMLOptionsCollection`オブジェクトは、`add`メソッドを用いて`select`要素に`option`要素（または`optgroup`要素）を追加できます。`insertBefore`と似ていますが、挿入位置を数値で指定できるのが特徴です。

#### `HTMLOptionsCollection`の数値プロパティへの代入

`HTMLOptionsCollection`は、`add`メソッドを使うほかにも、配列のように数値を名前とするプロパティに直接`option`要素を代入することで要素を挿入するという操作をサポートしています。

```js:HTMLOptionsCollectionの数値プロパティの例
const select = document.createElement("select");
const option = document.createElement("option");
option.textContent = "hi";

select.options[2] = option;

console.log(select); // <select><option></option><option></option><option>hi</option></select>
```

#### `HTMLSelectElement#add`, `HTMLSelectElement`の数値プロパティ

これらは`HTMLOptionsCollection`と同じ挙動をしますが、レギュレーションは別扱いとなります。

#### `HTMLOptionElement#text`

`option`要素を表す`HTMLOptionElement`の`text`プロパティは、例によって自身の子をテキストノードで置換することができます。

#### `HTMLTextAreaElement#defaultValue`

`textarea`要素は子に書かれたテキストが入力欄の初期値になるという特徴を持ちます。この初期値は`defaultValue`プロパティで表され、これを変更するとDOM上の子要素がしっかりと変更されます。

#### `HTMLOutputElement#defaultValue`, `HTMLOutputElement#value`

`output`要素（使ったことありますか？）も同様です。ただし、値を表示するという特性上`defaultValue`と`value`の両方がDOM上の子要素に影響を及ぼします。

#### `HTMLFormElement#reset`

`form`要素の`reset`メソッドはフォームをリセットして初期状態に戻すメソッドです。このメソッドを通じて`output`要素をリセットする際にテキストノードが生成される可能性があります。

```js:resetでノードが挿入される例
const form = document.createElement("form");
const output = document.createElement("output");
output.defaultValue = "hi";
form.append(output);
console.log(form.innerHTML); // <form><output>hi</output></form>

output.value = "" // outputの子要素が消去される
console.log(form.innerHTML); // <form><output></output></form>

// formをリセットする
form.reset();
// optionの中身が復元される
console.log(form.innerHTML); // <form><output>hi</output></form>
```

#### `HTMLScriptElement#text`

`script`要素を表す`HTMLScriptElement`ノードは`text`プロパティを持ち、これは中身のスクリプトが文字列で入っています。これに文字列を代入することでDOMの書き換えを発生させることができます。

ただ、中身を書き換えたからといってスクリプトが再実行されたりとかそういういうことはないようです。

### DOM Parsing and Serialization

HTMLとDOMの本体以外にも細々した仕様はいろいろありますが、今回の話に関わってくるのはこの[DOM Parsing and Serialization](https://w3c.github.io/DOM-Parsing/)くらいです。この仕様からは**3**種類のノード挿入方法が提供されています。

#### `InnerHTML#innerHTML`

非常に有名な`innerHTML`です。HTML文字列を代入するとそれをパースしてDOMノードを構築して子要素にしてくれます。当然ながら子要素をノードに追加することができます。

#### `Element#outerHTML`

`outerHTML`も似た機能で、これは`outerHTML`がセットされた要素自身を新しくパースして作られた要素で置換します。

#### `Element#insertAdjacentHTML`

上のほうで出てきた`insertAdjacentElement`などと似ていますが、こちらはやはりHTML文字列を解析して要素を作ってくれます。

## まとめ

ということで、HTMLとDOMで木構造にノードを挿入する方法全**38**種類を列挙しました。全部言えたらまさにあなたはDOMマスターと言えるでしょう。

ただ筆者の調査にも間違いや抜け漏れがあるかもしれません。筆者を超えるDOM力を持つ方からのご指摘をお待ちしています。

**Q.** DOMマスターになったら実務で役に立つんですか？
**A.** さあ……