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" のままで、レンダリングも標準準拠モードでレンダリングされる
まとめると
- 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) とかしちゃうとダメ
本来はやらなくても良い処理なので、後で削るかも。