スキップしてメイン コンテンツに移動

JavaScript で再帰呼び出しを使わずに文書ツリーを辿る例

JavaScript で文書ツリーを辿るコード例

再帰呼び出しは、短く書け読みやすいのですが、関数を多段で呼び出すコストが発生します。パフォーマンスの求められるところでは不適当です。本記事では再帰呼び出しをせずに、一度の関数呼び出しと do~while ループでノードツリーを辿る例を紹介します。

次の例では、与えらえたノード自身を引数に onReachNode 関数をコールバックします。以降は、最初に見つかった子ノードから同様にコールバックしていきます。

コールバックで true を返すと探索を終了します。コールバック中に現在のノードより前方にノードを挿入したり、前方のノードを除去すると index が乱れて正しく動作しません。

function walkAllDescendantNodes(node, onReachNode){
  var depthX2 = 0,
      childNodes = [node],
      combiList = [-1, childNodes],
      child, childsChildNodes;

  do {
    child = childNodes[++combiList[depthX2 + 0]];
    if (!child) {
      combiList.length = depthX2;
      depthX2 -= 2;
      childNodes = combiList[depthX2 + 1];
    } else {
      if (onReachNode(child) === true) {
        break;
      };
      childsChildNodes = child.childNodes;
      if (childsChildNodes && childsChildNodes.length) {
        depthX2 += 2;
        combiList[depthX2 + 0] = -1;
        combiList[depthX2 + 1] = childNodes = childsChildNodes;
      };
    };
  } while (0 <= depthX2);
};

.nextSiblings, .firstChild を使って全てのノードを辿る方法も考えられます。しかし今回は、Virtual DOM で使うコードの切り出しの為、Array で実装されている childNodes を利用する方が低コストという判断でした。

生 DOM では .nextSiblings, .firstChild を使う方がハイパフォーマンスと思われます。

次のコードを加えて、ブラウザのデベロッパーコンソールで実行してみます。

walkAllDescendantNodes(
  document,
  function(node){
    console.log(node.tagName || node.nodeValue || node.nodeType);
  }
);

実行結果例です。全てのノードを辿れているようです。

9
10
HTML
HEAD
META
LINK
LINK
META
LINK
TITLE
クラウド番外地
META
META
.
.
.

参考リンク

  1. Babel の末尾再帰最適化を試す