IEでコメントノードを事前に除去し速度を稼ぐ

uupaa.js 0.7 の修正履歴です。興味がない方は読み飛ばしてください。

ついったーから転記

HTML load完了 ⇒ コメントノード除去 ⇒ querySelectorAll高速化 というアイデアが浮かんだ。IE の getElementsByTagName("*") はコメントノードを列挙するので、その対策が不要に + 全体のノード数が減るのでちょっと省メモリかも

コメントノードとは API からそもそも列挙できないハズのもの + DOM トラバーサル時に邪魔な存在 なので、人間から見ればあってもいいけど、js からみれば要らない子。コメントノードに依存した処理ってのもちょっと考えづらいから、いけると思うんだけど

IE6 だとコメントノードの存在が http://css-bug.jp/win/ie/ver6/0424/ こういうのも引き起こすらしいので、むしろ積極的に除去してもいいかなぁと

<!doctype html> の nodeType はDOCUMENT_TYPE_NODE(10)が正解なんだけど、IE8 だと COMMENT_NODE(8)。IE6 だと <!--ctype ht--> として認識されてる

<!comment> という形のコメントノードもどきが HTML に埋まっていると、Fx3.5, IE6〜8 ではコメントノード、WebKit では無視され、Opera10.10 では tagName や localName が空の(本来ありえない) ELEMENT_NODE になる。Opera の挙動はバグだと思うんだけど

Opera10.10では、<body><!comment></body> とすると <body></></body> といったDOMツリーが生成され、body.outerHTML は "<BODY>> </BODY>" のように < と > の対応さえ取れていないHTMLを返す

<!--[if gte IE 6 ]><p>IE6+</p><![endif]--> は HTMLパース時点でプリプロセッサ(とよんじゃうけど)が処理するようで、DOM ツリー上にはコメントノードとして登場しません(in IE)

IE6〜8 で パース完了(DOMContentLoaded相当)後に、 <!doctype html> を除去しても document.compatMode = "CSS1Compat" のままで、レンダリングも標準準拠モードでレンダリングされる

via http://twitter.com/uupaa/status/6285064102

まとめると

  • IE は document.getElementsByTagName("*") で コメントノードまで列挙しちゃう。
    • そのため uu.tag("*") とした場合に、IE だけ余計なコメントノードを読み飛ばす処理が入ってる。
// uu.js

// inner - getElementsByTagName for legacy browser(IE6, IE7, IE8)
function uutaglegacy(expr, ctx) {
  var nl = (ctx || doc).getElementsByTagName(expr),
      rv = [], ri = -1, v, i = 0;

  if (expr !== "*") {
    rv = _toary(nl);
  } else { // [IE] getElementsByTagName("*") has comment nodes
    while ( (v = nl[i++]) ) {
      (v.nodeType === 1) && (rv[++ri] = v); // 1: ELEMENT_NODE
    }
  }
  return rv;
}
  • document.querySelectorAll("*") でも同じようなオーバーヘッドがある
    • じゃあ、コメントノードを除去しとけばいいんじゃない?
      • コメントノードを一括で除去しても問題なさそう
  • でも <!doctype html> を削るのは止めておこう(説明が面倒だし何かあっても困るから)

修正前

// uu.js

// inner - prebuild nodeid
uuready(function() {
  var nodes = uu.tag("*"), v, i = 0;

  while ( (v = nodes[i++]) ) {
    uunodeid(v);
  }
}, 2); // 2: high order

修正後

// uu.js

// inner - prebuild nodeid and remove comment node(IE only)
uuready(function() {
  var live = _html.getElementsByTagName("*"), v, w, ary = [], i = 0, j = 0;

  while ( (v = live[i++]) ) {   // live NodeList
    w = v.nodeType;
    w === 1 ? uunodeid(v) :     // 1: ELEMENT_NODE
    w === 8 ? ary.push(v) : 0;  // 8: COMMENT_NODE (IE only)
  }
  while ( (v = ary[j++]) ) {    // [IE] static NodeArray
    v.parentNode.removeChild(v);
  }
}, 2); // 2: high order
  • _html.getElementsByTagName("*") で、html要素以下のコメントノードのみ除去対象とする
  • while を2つ使ってるのは、getElementsByTagName が Live なノードリストを返すため
    • 最初の while ループ内で、v.parentNode.removeChild(v) とかしちゃうとダメ

本来はやらなくても良い処理なので、後で削るかも。