DOMおれおれAdvent Calendar 2015 – 14日目分
TreeWalkerというオブジェクトがあります。その名の通り、(DOMの)ツリーを歩き回るオブジェクトです。
例えばこんな構成。(インデント等によるテキストノードはないものとします。)
div#root
+ div#el-1
+ div#el-1-1
+ div#el-1-1-1
あるノード(#root)を起点に、長子の子孫を巡り(#el-1→#el-1-1→#el-1-1-1)、また戻ってくる(#el-1-1→#el-1→#root)という処理が必要だとします。
普通に関連ノードを参照して移動していくコードはこんな感じです。
var origin = document.querySelector('#root');
var current = origin;
// 子孫を巡る
while (current.firstChild) {
current = current.firstChild;
console.log(current.id);
}
// 祖先を巡る
while (current.parent && current !== origin) {
current = current.parent;
console.log(current.id);
}
何も問題ないように見えますか? 実はこれ、はい、何も問題ありません。まあ問題はないんですが、せっかくなのでこれをTreeWalkerを使っておきかえるとこんな感じです。
var origin = document.querySelector('#root');
var walker = document.createTreeWalker(origin);
// 子孫を巡る
while (walker.firstChild()) {
console.log(walker.currentNode.id);
}
// 祖先を巡る
while (walker.parentNode()) {
console.log(walker.currentNode.id);
}
TreeWalkerが指し示すノードは currentNode に格納されており、この内容がくるくる変わります。 parentNode() はプロパティではなくメソッドで、 walker の状態を変更する副作用を持ちます。
ちなみに戻り値に次の currentNode に相当するものを返します。対象が存在しない場合は null を返し、 currentNode の値を変更しません。
全部めぐる
せっかくなので長子だけじゃなくて全部歩き回るコードも載せておきます。
var walker = document.createTreeWalker(document.body);
walkThrough(walker);
function walkThrough(walker) {
if (walker.firstChild()) {
do {
var node = walker.currentNode;
// 要素ノードなら要素名を出力し、子の子を出力
if (node.nodeType === node.ELEMENT_NODE) {
console.log(node, node.tagName);
walkThrough(walker);
}
// 文字列ノードなら文字列を出力
else if (node.nodeType === node.TEXT_NODE) {
var text = String.trim(node.nodeValue);
if (text) {
console.log('#', text.slice(0, 127));
}
}
} while(walker.nextSibling());
walker.parentNode();
}
}
その他
document.createTreeWalker()で作ります。ルートになるノードの指定が必須です。- 第二引数でフィルターを指定する事ができます。
- 例:
document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT) - フィルターは
|で区切って複数指定できます。
- 例:
- ルートとされたノードは
walker.rootに格納されます。 - ルートとされたノードより上位へはたどれません。
walker.parentNode()がnullを返します。
使い道
んー特に思い付きませんでした。
同じインスタンスを使い続けられるので、参照の管理が楽なのが利点?
環境
IE 9+。その他問題ないようです。
