まるでjQueryみたい!ネイティブなDOM操作メソッドもこんなに進化していた!

2017/09/05

Giulio Mainardi

120

Articles in this issue reproduced from SitePoint
Copyright © 2017, All rights reserved. SitePoint Pty Ltd. www.sitepoint.com. Translation copyright © 2017, KADOKAWA ASCII Research Laboratories, Inc. Japanese syndication rights arranged with SitePoint Pty Ltd, Collingwood, Victoria,Australia through Tuttle-Mori Agency, Inc., Tokyo

「脱jQuery」をめぐる議論は何年も前からありますが、jQueryの使い勝手のよさは無視できません。ところが、WHATWGのDOM StandardではjQuery風の使いやすいAPIが考案されています。

jQueryがリリースされた2006年当時、人気になった理由の1つは、jQueryを使えばDOM要素を簡単に選択、移動し、コンテンツを変更できたからでした。当時はIE7に悩まされ、ECMAScript 5が出てくるのはまだ数年後のことでした。

幸いなことに、当時と比べるとブラウザーは標準に対応しており、ネイティブなJavaScriptの機能も飛躍的に進化しています。状況が改善されるにつれて、jQueryはいまでも必要か? と耳にすることが増えました。ここではその議論はしませんが、jQueryという優れたライブラリーから着想を得た、DOM要素を操作するネイティブのメソッドを6つ紹介します。

ネイティブなDOM操作メソッドと、jQueryのメソッドとの共通点と違いを説明します。jQueryの便利さを理解した上で、素のJavaScriptを記述する力を身につけられれば幸いです。

1. append()

appendメソッドはDOMツリーにノードを挿入する操作です。append(追加)の名前のとおり、渡された引数を、呼び出されたノードのリストに追加します。次のサンプルを見てください。

const parent = document.createElement('div')
const child1 = document.createElement('h1')
parent.append(child1)
parent.outerHTML
// <div><h1></h1></div>

const child2 = document.createElement('h2')
parent.append(child2)
parent.outerHTML
// <div><h1></h1><h2></h2></div>

ネイティブのappendChildとの違いは、append()は同時に複数の引数を渡すことができ、jQuery appendメソッドのように子のリストに対してそれぞれノードを追加できる点です。先ほどのスニペットに続けてみます。

const child3 = document.createElement('p')
const child4 = document.createElement('p')
const child5 = document.createElement('p')
parent.append(child3, child4, child5)
parent.outerHTML
/* Outputs:
<div>
  <h1></h1>
  <h2></h2>
  <p></p>
  <p></p>
  <p></p>
</div>
*/

引数は文字列でも構いません。appendChild()では冗長な構文を書く必要がありました。

parent.appendChild(document.createTextNode('just some text'))

append()では短い記述で同じ結果が得られます。

parent.append('just some text')
parent.outerHTML
/* Outputs:
<div>
  <h1></h1>
  <h2></h2>
  <p></p>
  <p></p>
  <p></p>
  just some text
</div>
*/

文字列はテキストノードに変換されるので、HTML要素には対応しません。

parent.append('<p>foo</p>')
parent.outerHTML
/* Outputs:
<div>
  <h1></h1>
  <h2></h2>
  <p></p>
  <p></p>
  <p></p>
  just some text
  &lt;p&gt;foo&lt;/p&gt;
</div>
*/

マークアップ記号も変換されます。対応したノードが生成されて、DOMツリーに追加されるjQueryメソッドとは対照的です。

通常は、すでにツリー内にあるノードが追加された場合、もともとあった場所からは削除されます。

const myParent = document.createElement('div')
const child = document.createElement('h1')
myParent.append(child)
const myOtherParent = document.createElement('div')
const myOtherParent.append(child)
myOtherParent.outerHTML
// <div><h1></h1></div>

myParent.outerHTML
// <div></div>"

append()appendChild()の最後の違いは、前者は追加されたノードを戻り値として返しますが、後者はundefinedを返すことです。

2. prepend()

prependメソッドはappend()とよく似ています。同じように子要素を追加しますが、prependメソッドの場合は先頭に追加されます。メソッドによって呼び出されたリストの最初の子要素の前に追加されます。

const parent = document.createElement('div')
const child1 = document.createElement('p')
parent.prepend(child1)
parent.outerHTML
// <div><p></p></div>

const child2 = document.createElement('h2')
parent.prepend('just some text', child2)
parent.outerHTML
/* Outputs:
<div>
  just some text
  <h2></h2>
  <p></p>
</div>
*/

メソッドの戻り値はundefinedで、対応するjQueryのメソッドはprepend()です。

3. after()

もう1つの挿入メソッドはafterです。特定の親ノードが存在している状態で子ノード上で呼び出します。次のサンプルで、並列な兄弟ノードとして挿入されているのを確認してください。

const parent = document.createElement('ul')
const child = document.createElement('li')
child.append('First item')
parent.append(child)
child.after(document.createElement('li'))
parent.outerHTML
// <ul><li>First item</li><li></li></ul>

戻り値はundefinedで、対応するjQueryでの操作はafter()です。

4. before()

beforeメソッドもafter()と似ていますが、子ノードの直前にノードを挿入します。

const parent = document.createElement('ul')
const child = document.createElement('li')
child.append('First item')
parent.append(child)

const child1 = document.createElement('li')
child1.append('Second item')

const child2 = document.createElement('li')
child2.append('Third item')

child.before(child1, child2)

parent.outerHTML
/* Outputs:
<ul>
  <li>Second item</li>
  <li>Third item</li>
  <li>First item</li>
</ul>
*/

今回も戻り値はundefinedで、対応するjQueryメソッドはbefore()です。

5. replaceWith()

DOMのノードを置き換えたいこともあるでしょう。たいていが子要素なので、サブツリーのDOM要素を丸ごと置き換えます。紹介したメソッドを知らなければ、replaceChild()を使っていました。

const parent = document.createElement('ul')
parent.innerHTML = `
  <li>first</li>
  <li>second</li>
  <li>third</li>
`
parent.outerHTML
// <ul><li>first</li><li>second</li><li>third</li></ul>"

const secondChild = parent.children[1]

const newSecondChild = document.createElement('li')
newSecondChild.innerHTML = '<a href="#">second</a>'

secondChild.parentNode.replaceChild(newSecondChild, secondChild)
parent.outerHTML
/* Outputs:
<ul>
  <li>first</li>
  <li><a href="#">second</a></li>
  <li>third</li>
</ul>
*/

innerHTMLとテンプレートリテラルのおかげでツリー構造が記述しやすくなりました。

replaceWithなら、同じ操作を冗長性を省いて実行できます。

parent = document.createElement('ul')
parent.innerHTML = `
  <li>first</li>
  <li>second</li>
  <li>third</li>
`
secondChild = parent.children[1]

newSecondChild = document.createElement('li')
newSecondChild.innerHTML = '<a href="#">second</a>'

secondChild.replaceWith(newSecondChild)

このメソッドは構文を短くできるだけでなく、複数の引数を受け入れてノードを別のノードのリストに置き換えられます。前述のJavaScriptに記述を追加して結果を確認します。

const newerSecondChild = document.createElement('li')
newerSecondChild.append('another item')
const newThirdChild = document.createElement('li')
newThirdChild.append('yet another item')
newSecondChild.replaceWith(newerSecondChild, newThirdChild)
parent.outerHTML
/* Outputs:
<ul>
  <li>first</li>
  <li>another item</li>
  <li>yet another item</li>
  <li>third</li>
</ul>
*/

戻り値はundefinedです。同じ名前のjQueryメソッドがあるので比べてみてください。

remove()

DOMツリーからノードを削除する場合、昔のやり方ならremoveChild()でしょう。関数名のとおり、親のノード上で呼び出して指定したノードを削除します。

n.parentNode.removeChild(n)

remove()なら、操作がさらにシンプルになります。

const parent = document.createElement('ul')
const n = document.createElement('li')
parent.append(n)
parent.outerHTML
// <ul><li></li></ul>

n.remove()
parent.outerHTML
// <ul></ul>

jQueryのremoveとの違いは、削除されたノードと関連付けられたインベントリスナーの扱いです。jQueryでは要素と関連付けられたイベントとデータはすべて削除されますが、ネイティブメソッドではイベントリスナーには手を付けません。

const parent = document.createElement('ul')
const n = document.createElement('li')
parent.append(n)

n.addEventListener('test', console.log.bind(console))

const e = new Event('test')
n.dispatchEvent(e)
Event {isTrusted: false, type: "test", ...

n.remove()
n.dispatchEvent(e)
Event {isTrusted: false, type: "test", ...

この動作はjQueryのdetachメソッドにより近いものです。

ブラウザーサポート

この記事の執筆時点で、最初に紹介した5つのメソッド、prepend()append()before()after() 、replaceWith()サポートしているデスクトップブラウザーです。

  • Chromeはバージョン54から実装
  • Firefoxはバージョン49からサポート
  • Safariはバージョン10からサポート
  • Operaはバージョン41からサポート
  • Internet Explorer、Microsoft Edgeはサポートなし(Edgeでは現在開発中

removeメソッドには幅広いサポートがあります。Microsoft Edgeでもバージョン14から実装されます。

現在対応していないブラウザーでも、ポリフィルで対応できる場合もあります。childNode.jsはその1つですが、今回取り上げたメソッド用のほかのポリフィルもMDN上で見つけられます。

7. おまけのメソッド: insertAdjacentHTML

まとめの前に、insertAdjacentHTMLについても少し書きます。最初の4つの挿入メソッド(append()、prepend()、after()、before())と同じような挿入操作で、指定されたHTMLの文字列の挿入操作ができます。

const parent = document.createElement('div')
parent.insertAdjacentHTML('beforeend', '<p>A paragraph</p>')
parent.insertAdjacentHTML('beforeend', '<p>Another paragraph</p>')
parent.insertAdjacentHTML('afterbegin', '<p>Yet another paragraph</p>')

const grandParent = document.createElement('div')
grandParent.append(parent)

parent.insertAdjacentHTML('afterend', '<div class="after"></div>')
parent.insertAdjacentHTML('beforebegin', '<div class="before"></div><div class="before2"></div>')

grandParent.outerHTML
/* Outputs:
<div>
  <div class="before"></div>
  <div class="before2"></div>
  <div>
    <p>Yet another paragraph</p>
    <p>A paragraph</p>
    <p>Another paragraph</p>
  </div>
  <div class="after"></div>
</div>
*/

別の子ノードを追加するためにbeforebeginafterendの引数を使う場合は、先にparentノードを作ります。

insertAdjacentHTML()どの環境でも使えるので助かります。

最後に

jQueryに着想を得たDOMメソッドを簡単に紹介しました。ネイティブなDOMのAPIがどんな進化しているか、jQueryで実装していたものをどうすればネイティブメソッドで簡単に置き換えられるか、伝えられたらうれしいです。

本記事はSebastian Seitzが査読を担当しています。最高のコンテンツに仕上げるために尽力してくれたSitePointの査読担当者のみなさんに感謝します。

(原文:6 jQuery-inspired Native DOM Manipulation Methods You Should Know

[翻訳:前田類/編集:Livit

Copyright © 2017, Giulio Mainardi All Rights Reserved.

Giulio Mainardi

Giulio Mainardi

フロントエンド開発を勉強中です。

Loading...