読者です 読者をやめる 読者になる 読者になる

IEでのテキストノード走査の高速化

JavaScript

に釣られて。

HatenaStar.js 1380 行目 テキストノード走査

一番のボトルネックはやはりここですね。IEなので、こんな感じでベタに計測。

    makeTextNodes: function(c) {
        if (c.textNodes || c.textNodePositions || c.documentText) return;
        if (Ten.Highlight.highlighted) Ten.Highlight.highlighted.hide();
        c.textNodes = [];
        c.textNodePositions = [];
        var isIE = navigator.userAgent.indexOf('MSIE') != -1;
        var texts = [];
        var pos = 0;
        var st = new Date*1;
        (function(node, parent) {
            if (isIE && parent && parent != node.parentNode) return;
            if (node.nodeType == 3) {
                c.textNodes.push(node);
                texts.push(node.nodeValue);
                c.textNodePositions.push(pos);
                pos += node.nodeValue.length;
            } else {
                var childNodes = node.childNodes;
                for (var i = 0; i < childNodes.length; i++) {
                    arguments.callee(childNodes[i], node);
                }
            }
        })(document.body);
        c.documentText = texts.join('');
        c.loaded = true;
        alert(new Date*1 - st);
    },

http://b.hatena.ne.jp/amachang/ において、IE7では、Core2 Duo 2GHz の PC でも4秒弱かかっています。

IE6,IE7でも動く高速化

    makeTextNodes: function(c) {
        if (c.textNodes || c.textNodePositions || c.documentText) return;
        if (Ten.Highlight.highlighted) Ten.Highlight.highlighted.hide();
        var textNodes = c.textNodes = [];
        var textNodePositions = c.textNodePositions = [];
        var isIE = navigator.userAgent.indexOf('MSIE') != -1;
        var texts = [];
        var pos = 0;
        var st = new Date*1;
        var fn = function(node, parent) {
            if (isIE && parent && parent != node.parentNode) return;
            var nodeType = node.nodeType;
            if (nodeType == 3) {
                textNodes.push(node);
                texts.push(node.nodeValue);
                textNodePositions.push(pos);
                pos += node.nodeValue.length;
            } else if (nodeType == 1) {
                var childNodes = node.childNodes;
                for (var i = 0, l = childNodes.length; i < l; ++i) {
                    fn(childNodes[i], node);
                }
            }
        };
        fn(document.body);
        c.documentText = texts.join('');
        c.loaded = true;
        alert(new Date*1 - st);
    },

これで、http://b.hatena.ne.jp/amachang/ において、IE7Core2 Duo 2GHz で、2秒弱に高速化できました。

ざっくり解説

基本ですが、.を減らせるだけ減らします。修正点は以下の通り。

  • 変数宣言
c.textNodes = [];
c.textNodePositions = [];
//
c.textNodes.push(node);
c.textNodePositions.push(pos);

var textNodes = c.textNodes = [];
var textNodePositions = c.textNodePositions = [];
//
textNodes.push(node);
textNodePositions.push(pos);

と変数を宣言して、textNodes、textNodePositionsにそれぞれpushします。

  • ループの最適化
for (var i = 0; i < childNodes.length; i++) {

は、childNodes.lengthを変数に入れておきます。定番ですね。

for (var i = 0, l = childNodes.length; i < l; ++i) {
  • 再帰の最適化
(function(node, parent) {
//
arguments.callee(childNodes[i], node);
//
})(document.body);

arguments.calleeによる再帰ではなく、関数オブジェクトを作成します。

var fn = function(node, parent) {
//
fn(childNodes[i], node);
//
};
fn(document.body);

以上です。
あとはnodeType == 1のチェックを入れたけど、これは不要だったかな。
ちなみに再帰処理の展開も試してみたけど、あまり差は出なかった。


あ、あと一応言っておくと、こういう処理はクライアントサイドでやるべきじゃないだろって話はあるよね。
まあ言うのは簡単だけど、実際にはブラウザごとにテキストノードの扱い方が違うので難しいのですが。。
多少現実的な方法としては、全部のテキストをspanでラップして、spanを走査するとか? normalizeもしないといけないから、やっぱり難しいけど。これは、はてなスターの話だった。はてなだけのHTMLの話じゃないんだ。