DOMおれおれAdvent Calendar 2015 – 14日目分

2015-12-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+。その他問題ないようです。

参考