【Javascript活用講座】もっとHTML要素を上手く操作するためのappendChildの使い方


2021/12/22
蛸壺の技術ブログ|もっとHTML要素を上手く操作するためにappendChildメソッドをカスタマイズする

久々にjavascriptのテクニカルな話題です。

今回はHTMLを直接スクリプトからDOM操作するために必須となる
appendChildメソッドにスポットを当てます。

特にappendChildの面白い特徴である
メソッドチェーンを理解することで、より効率的なDOM操作を検討してみましょう。


appendChildメソッドの基本

appendChildメソッドはその名の通りHTML要素(ノード)の下に子要素(ノード)を追加するだけです。

以下のHTMLファイルをブラウザで直に開いて確認してみましょう。

            
            <!DOCTYPE html>
<html lang="ja">
<html>
    <meta charset="utf-8"/>
    <body>
        <div id="wrapper"></div>
        <script>
            document.addEventListener("DOMContentLoaded", () => {
                const parent = document.getElementById('wrapper');

                const child = document.createElement('p');
                child.textContent = 'appendChildメソッドのテストです。';

                //👇親ノードに子ノードを追加する
                parent.appendChild(child)
            });
        </script>
    </body>
</html>
        

結果は以下のようになります。

合同会社タコスキングダム|蛸壺の技術ブログ

このようにappendChildメソッドはとてもシンプルな機能です。

シンプルな故に、ほとんどの人は
appendChildメソッドの返り値についてほぼ利用することもなく、普段は意識してはいないと思います。

今回はこのポイントを良く理解してみましょう。

結論から先に言うと、
「appendChildは追加した子ノードを返す」ということをします。

先程のindex.htmlのスクリプト中身を以下に変更します。

            
            document.addEventListener("DOMContentLoaded", () => {
    const parent = document.getElementById('wrapper');

    const child = document.createElement('p');
    child.textContent = 'appendChildメソッドのテストです。';

    child === parent.appendChild(child) && console.log('オブジェクトが一致しました');
});
        
合同会社タコスキングダム|蛸壺の技術ブログ

ということで、appendChildの返り値が追加した子要素と一致しました。

この返り値に一体なんの意味が...と感じる人もいるかもしれません。

一つはappendChildで最後に追加した要素を確認したいとき、もう一つは追加した子要素の下にさらに孫要素を追加したいとき、に使うことができます。

よってappendChildが追加した子要素そのものを返すため、先程のコードならば以下のようにワンライナーで書くことができます。

            
            document.addEventListener("DOMContentLoaded", () => {
    document.getElementById('wrapper').appendChild(document.createElement('p')).textContent = 'appendChildメソッドのテストです。';
});
        


createElementをカスタマイズする

もっとDOM操作をjavascriptから使いやすくするためは、createElementメソッドを拡張したほうが良いでしょう。

これは標準のcreateElementは一つの引数にHTMLタグ名を指定してHTML要素を生成するだけですので、あまり使い勝手の良いメソッドとは言えないことが理由です。

ということで
独自定義のcreateElementメソッドを以下のように作成します。

            
            const createElement = (tagName, attrs) => {
    const _e = document.createElement(tagName);
    if (attrs) {
        for (let attr in attrs) { _e[attr] = attrs[attr]; }
    }
    return _e;
};
        

これで前節のスクリプト部分はもっと見通しが良くすることができて、

            
            const createElement = (tagName, attrs) => {
    const _e = document.createElement(tagName);
    if (attrs) {
        for (let attr in attrs) { _e[attr] = attrs[attr]; }
    }
    return _e;
};

document.addEventListener("DOMContentLoaded", () => {
    document.getElementById('wrapper').appendChild(createElement('p', {
        textContent: 'appendChildメソッドのテストです。'
    }));
});
        
とかなりスッキリとコードが書けるようになりました。


appendChildメソッドチェーン

ここからが気持ち的に本題です。

先程のから説明してきた通り、appendChildメソッドには追加した子要素の参照がそのまま返ってくる性質を利用しすることで、ワンライナーでDOMを直列的に生成することが可能になります。

これをここでは
「appendChildメソッドチェーン」と呼ぶことにします。

一例を示すと、以下のようなスクリプトに変更します。

            
            const createElement = (tagName, attrs) => {
    const _e = document.createElement(tagName);
    if (attrs) {
        for (let attr in attrs) { _e[attr] = attrs[attr]; }
    }
    return _e;
};

document.addEventListener("DOMContentLoaded", () => {
    document.getElementById('wrapper')
        .appendChild(createElement('div', {
            innerHTML: 'これは最初のDIVです。'
        }))
        .appendChild(createElement('div', {
            innerHTML: 'これは2番めのDIVです。'
        }))
        .appendChild(createElement('div', {
            innerHTML: 'これは3番めのDIVです。'
        }))
        .appendChild(createElement('span', {
            innerHTML: 'これは3番めの中の最初のSPANです。'
        }));
});
        
生成されたDOMを確認すると以下のようになっています。

合同会社タコスキングダム|蛸壺の技術ブログ

ということで、appendChildメソッドチェーンを使えば容易にかつ効率的に階層的なDOMを作成することが可能となりました。


余談〜コンストラクタ関数でメソッドチェーンを拡張

先程まででappendChildメソッドチェーンでDOM階層の縦方向へ要素を繋げることができました。

欲を言えば、同じ階層にある要素をDOM構造の横のつながりも柔軟に増やしてみたいと思われるかも知れません。

この場合、
コンストラクタ関数を使ってオブジェクトのprototypeを拡張することで、独自のメソッドチェーンを組み込むことができます。

Class構文でも同じように出来るのですが、classを使うことが出来るのはECMAScript 6以降ですので、取り扱いには注意が必要です。

例えばDOMBuilderという名前でコンストラクタ関数を作ります。

            
            function DOMBuilder(element) {
    this.element = element;
};
DOMBuilder.prototype = {
    appendChildren: function(domSeed) {
        const children = [];
        for (const el of domSeed) {
            const child = this.element.appendChild(createElement(el[0], el[1]));
            children.push(new DOMBuilder(child));
        }
        return children;
    }
};
        
ポイントはプロトタイプで追加したappendChildrenメソッドの返り値が、追加した子ノードのDOMBuilder関数のインスタンスの入った配列にしている点です。

使い方は若干複雑になるのですが、以下のように使います。

            
            const createElement = (tagName, attrs) => {
    const _e = document.createElement(tagName);
    if (attrs) {
        for (let attr in attrs) { _e[attr] = attrs[attr]; }
    }
    return _e;
};

function DOMBuilder(element) {
    this.element = element;
};
DOMBuilder.prototype = {
    appendChildren: function(domSeed) {
        const children = [];
        for (const el of domSeed) {
            const child = this.element.appendChild(createElement(el[0], el[1]));
            children.push(new DOMBuilder(child));
        }
        return children;
    }
};

document.addEventListener("DOMContentLoaded", () => {
    (new DOMBuilder(document.getElementById('wrapper')))
        .appendChildren([
            ['div', {innerHTML: 'これは最初のDIVです。'}],
            ['div', {innerHTML: 'これは2番目のDIVです。'}],
            ['div', {innerHTML: 'これは3番目のDIVです。'}],
        ]).forEach(dom => {
            dom.appendChildren([
                ['p', {textContent: `${dom.element.innerHTML} の最初のPです。`}],
                ['p', {textContent: `${dom.element.innerHTML} の2番めのPです。`}],
            ]);
        });
});
        

このindex.htmlを開くと、以下のようなDOM構造になっていることが分かります。

合同会社タコスキングダム|蛸壺の技術ブログ

こうしてみるとappendChildだけで構成された、もっとも簡単なJavascriptフレームワークと見えなくもありません。


まとめ

今回はDOM操作でもっとも使うであろうappendChildメソッドの使い方を深く考察してみました。

確かにReactやAngularなどの高機能JSフレームワークならば作法に従って、複雑なDOM生成も簡単に操作できるのですが、今回のような「HTMLの標準関数」を良く理解しておくとなにかの時に役に立つと思います。

参考サイト

Node.appendChild | MDN

createElementメソッドが微妙なので汎用的な関数にする

記事を書いた人

記事の担当:taconocat

ナンデモ系エンジニア

主にAngularでフロントエンド開発することが多いです。 開発環境はLinuxメインで進めているので、シェルコマンドも多用しております。 コツコツとプログラミングするのが好きな人間です。