DOMTokenListとspace-separated tokens
先日のclassList
(DOMTokenList
) に関するエントリでは、DOMTokenList
も用途によっては取り回しにくい面があると述べました。
結論からいえば、DOMTokenList
各関数は空白文字区切りトークン (space-separated tokens) をとれないため、同一属性の複数属性値を操作しにくい場合があります。
classList
とgetElementsByClassName()
の比較
ここからはDOMTokenList
の利用例としてclassList
に焦点を絞り、同じくclass
属性に関係するgetElementsByClassName()
と比較することで、とりうる引数の違いを明らかにします。
次の(X)HTML断片を例に説明します:
<p class="foo bar">first</p>
<p class="bar baz">second</p>
<p class="foo baz">third</p>
まずはgetElementsByClassName()
から見ていきます。getElementsByClassName()
もclassList
と同様にHTML5で定義されたインタフェイスです(こちらは実装が先行したパターン)。getElementsByClassName()
は引数にclass
属性値をとり、それらを持つ要素ノードを収集します。
getElementsByClassName()
の引数は、空白文字区切りトークン (space-separated tokens) です。つまり、複数のclass
属性値を空白文字 (space characters) で区切り、AND条件として与えることができます。
次のコード断片は、空白文字で区切られたclass
属性値foo
とbar
を両方持つ要素ノードのみを収集するgetElementsByClassName()
の実行例です(CSSセレクタで例えれば.foo.bar
と同様であると考えることもできます):
var elems = document.getElementsByClassName("foo bar"),
nelems = elems.length,
ret = [],
e;
for (e = 0; e < nelems; e++) {
ret[e] = elems[e].className;
}
// "foo bar" のみアラート
alert(ret.join(', '));
一方、classList
の各関数(add()
、remove()
、contains()
、toggle()
)は、それぞれclass
属性値 (DOMString
) のトークンを引数とします。これは仕様にも明記されているように、空白文字区切りトークン (space separated tokens) ではないので、空白文字を含めることができません(引数に空白文字が含まれている場合、例外INVALID_CHARACTER_ERR
が投げられる)。
次のコード断片は、containes()
により対象要素ノードが特定のclass
属性値を含んでいるか否かをテストしようとしている例です。しかし、最終行のcontains()
引数が空白文字を含んでいるため、結果的に例外が投げられてしまいます:
var elem = document.querySelector(".foo.bar");
elem.classList.contains("baz"); // 問題なし、false を返す
elem.classList.contains("foo bar"); // 例外が投げられる
文字列リテラルの分割と「空白文字」の違い
前述のように、DOMTokenList
各関数は複数の属性値を同時に扱うことができません。よって、一度に複数の属性値を操作したい場合は、対象とする属性値を(ループを回すなどして)1つずつ与える必要があります。
例えば、文字列リテラル"alpha beta gamma"
を空白文字区切りトークンとみなして、それらすべてが特定要素ノードのclass
属性値として含まれるかどうかをclassList
のcontains()
でチェックしたいとしましょう。このとき、文字列リテラル"alpha beta gamma"
をsplit()
を用いて分割、Array
オブジェクト化して配列の各アイテムごとに処理を行うようにする――といった実装を考えることができます。次のコード断片は、前述の実装を試してみた例です:
var target = document.getElementById("dummy"),
classes = "alpha \t \n beta \f \r gamma".split(/[ \t\n\f\r]+/),
nclasses = classes.length,
cl = target.classList,
result = true,
c;
// 線形探索
for (c = 0; c < nclasses; c++) {
if (!cl.contains(classes[c])) {
result = false;
break;
}
}
// 1つでも含まれていなければ false をアラート
alert(result);
こうした実装は、単純に考えればネイティブコードに引数を与えるだけの場合よりオーバーヘッドが増えやすくなります(もちろん、時と場合によって影響の度合いは様々です)。また、文字列リテラルをsplit()
を用いて分割する際にはHTML5における空白文字 (space characters) の仕様を知っておく必要があります。HTML5における空白文字とJavaScript(ECMAScript 5 ないし ECMAScript 3)の正規表現における\s
の範囲が異なっているからです。
DOMTokenList
は便利ですが、特に複数の属性値を操作する場合には注意が必要です。しかし、前述したような仕様の詳細を念頭において使いさえすれば問題はないでしょう。