Movatterモバイル変換


[0]ホーム

URL:


IT戦記

プログラミング、起業などについて書いているプログラマーのブログです😚

はてなブックマークのコンテンツの JavaScript を高速化する

はじめに

「新はてなブックマーク」になったということで、とっても便利になったのですが、ブックマーク一覧ページ*1が若干JavaScript に時間が掛かっているみたいです。

というわけで

調査してみたいと思います。調査して、改善できそうなところは後で纏めて「はてなアイデア」にでも登録しようと思います。
この日記は調査しながら、過程を書いていくつもりです。

準備

まずは、人のサイトのJavaScript を書き換えて試してみるための環境を作ります。

作業用ディレクトリを作る

とりあえず、ホームに HatenaJS というディレクトリを作ります。

$ mkdir HatenaJS$ cd HatenaJS
CocProxy をダウンロードしてくる

以下から CocProxy というツールをダウンロードしてきます。
http://coderepos.org/share/wiki/CocProxy

$ wget http://svn.coderepos.org/share/lang/ruby/cocproxy/proxy.rb

CocProxy はid:cho45 が作った超絶便利ツールです。
ローカルに Proxy サーバーを立ち上げて、 Web サーバからの応答をローカルのファイルの内容に差し替えることができます。

CocProxy で差し替えるファイル用のディレクトリを作る

CocProxy は、 proxy.rb と同じディレクトリ内にある files サブディレクトリ内にファイルを置いておけば、自動で応答を差し替えてくれるようになります。

$ mkdir files
はてなブックマークで使っている以下のJavaScript をダウンロード

はてなブックマーク一覧ページで使っているJavaScript をダウンロードして、 filesディレクトリ内に入れます。

$ cd files$ wget http://s.hatena.ne.jp/js/HatenaStar.js$ wget http://b.hatena.ne.jp/js/DropDownSelector.js$ wget http://b.hatena.ne.jp/js/CSSChanger.js$ wget http://b.hatena.ne.jp/js/Hatena/Bookmark.js$ cd ..

今のところ作業ディレクトリ内は以下のような感じです。

$ tree.|-- files|   |-- Bookmark.js|   |-- CSSChanger.js|   |-- DropDownSelector.js|   `-- HatenaStar.js`-- proxy.rb1 directory, 5 files
CocProxy を起動して、ブラウザのプロキシを設定する

ruby で proxy.rb を起動します。以下のように、表示されます。

$ ruby proxy.rb Use default configuration.Port : 5432Dir  : files/Cache: trueRules:    1. #{File.basename(req.path_info)}    2. #{req.host}#{req.path_info}    3. #{req.host}/#{File.basename(req.path_info)}    4. .#{req.path_info}

これが完了したらlocalhost:5432 にプロキシサーバが立ち上がっているので、ブラウザに設定します。

準備完了

これで準備完了です。
あとは filesディレクトリ内のファイルを、書き換えればその結果をブラウザ上で確認できるようになります。
というわけで、実際に書き換えていきましょう。

まずは、Firebug でプロファイリングする

とりあえず、Firefox 3.0 を最初のターゲットにします。
JavaScript のパフォーマンスチューニングをする際に一番最初にやるべきことは、プロファイリングです。
手順は以下の通りです。

  1. Firebug のコンソールを開いて、上のほうにあるプロファイルボタンを押して、ページをリロード
  2. ページが読み込まれ、しばらくしてプロファイルボタンをもう一度押す
  3. 結果が表示される
プロファイリングの結果

結果が出ました

  • 「時間」は、その関数の開始から終わりまでの時間の合計
  • 「所有時間」は、その関数の開始から終わりまでの時間の合計から、自分の呼び出した関数の「時間」を引いたもの
  • たいていの場合は「所有時間」から重い場所を特定できる
結果の考察

はてなブックマークJavaScript の実行時間のほとんどが HatenaStar.jsということが分かりました。
まずは、 HatenaStar.js をカスタマイズしていきましょう。

重い箇所1:HatenaStar.js 1380 行目

    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;         (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;},
コードを読む

このコードは、

  • コンテンツ内の TextNode をすべて走査して、全体の textContent における TextNode の出現位置をキャッシュするためのもの
  • はてなスター」の「引用部分」を高速に探してハイライトするために使っている
  • Ten.Highlight の最初のオブジェクトが作られた時に一度だけ全実行される
  • 二個目のオブジェクト以降は、最初の条件文でリターンする

って感じですね。

改善してみる

まず、 DOM ツリーの走査がかなり重そうです。
XPath を使えるブラウザでは高速化できそうですね。
という訳で、まず以下のような判定を入れます。

/* Ten.Browser */Ten.Browser ={// XPath が使えるかどうかのフラグ    supportsXPath: !!document.evaluate,    isIE: navigator.userAgent.indexOf('MSIE') != -1,    isMozilla: navigator.userAgent.indexOf('Mozilla') != -1 && !/compatible|WebKit/.test(navigator.userAgent),    isOpera: !!window.opera,    isSafari: navigator.userAgent.indexOf('WebKit') != -1,    version:{        string: (/(?:Firefox\/|MSIE |Opera\/|Version\/)([\d.]+)/.exec(navigator.userAgent) ||[]).pop(),        valueOf:function(){return parseFloat(this.string)},        toString:function(){returnthis.string}}};

で、件の箇所を以下のようにXPath を使うように修正します。

    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;// XPath をサポートしていたらif (Ten.Browser.supportsXPath){// XPath で全てのテキストノードを取得するvar result =document.evaluate('descendant::text()',document.body,null, 7,null);// テキストノードの走査for (var i = 0; i < result.snapshotLength; i ++){var node = result.snapshotItem(i);                c.textNodes.push(node);                c.textNodePositions.push(pos);                pos += node.length;}// textContent は一発で             c.documentText =document.body.textContent ||document.body.innerText;            c.loaded =true;return;}        (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;},
結果を見る


458.3ms かかっていた処理が 91.584ms にも縮みました。
これはかなりよかったみたいですね。

重い箇所2:HatenaStar.js 1738 行目

    createButton:function(args){var img =document.createElement('img');for (var attrin args){            img.setAttribute(attr, args[attr]);}with (img.style){        cursor ='pointer';        margin ='0 3px';            padding ='0';             border ='none';            verticalAlign ='middle';}return img;},
コードを読む

コードは読むまでもなく、

  • 画像を作って
  • 属性の設定
  • style の設定

をしていますね。
このコードが 254 回呼び出されています。

解説

こんなシンプルなソースコードが何故重いかというと、Firefox でのJavaScript による img.src の設定が激重なのです。

改善してみる

これは、Firefox 固有の問題なのでブラウザを切り分けて対処します。Firefox では img を辞めて span を使うようにしてみました。

    createButton:function(args){// Firefox ならif (Ten.Browser.isMozilla){// クラスvar c = Hatena.Star.Button;// img の代わりに span 要素を使うvar img =document.createElement('span');// title 要素の設定            img.title = args.title;var style = img.style;// クラスに画像のキャッシュを持たせる            c.imageCache = c.imageCache ||[];// キャッシュに Image オブジェクトが入っているかvar cache = c.imageCache[args.src]if (!cache){// 無かったら作る                cache =new Image;                c.imageCache[args.src] = cache;// load したらフラグ立てる                cache.addEventListener('load',function(){ cache.loaded =true},false);                cache.src = args.src;}// 高さと幅を設定する関数function setStyle(){                style.width = cache.width +'px';                style.height = cache.height +'px';}// Image オブジェクトがロード済みだったらその場で呼び出す// 未ロードだったら、 load イベントリスナーに登録            cache.loaded ? setStyle() : cache.addEventListener('load', setStyle,false);// img 要素(置換要素)と同じ display を付ける            style.display ='inline-block';// background-image の指定            style.backgroundImage ='url(' + args.src +')';}else{var img =document.createElement('img');for (var attrin args){                img.setAttribute(attr, args[attr]);}}with (img.style){        cursor ='pointer';        margin ='0 3px';            padding ='0';             border ='none';            verticalAlign ='middle';}return img;},
もう一度プロファイリング


初回 237ms 二回目 173ms かかっていた createButton が 90ms にまで減りました。これも、けっこうききました。
と思ったら、 addAddButton 関数(ここで作った要素を挿入する箇所)の時間が逆に 60ms 増えていますね。これではダメですね。
この箇所は、諦めてとりあえず前の状態に戻しておきます。
Firefox による、画像の動的挿入が思いのは不可避なのでしょうか。。。

追記:解決策があったようです。

2008-11-27 - つれずれなるままに…

重い箇所3:HatenaStar.js 943 行目

    getElementStyle:function(elem, prop){var style = elem.style ? elem.style[prop] :null;if (!style){var dv =document.defaultView;if (dv && dv.getComputedStyle){try{var styles = dv.getComputedStyle(elem,null);}catch(e){returnnull;}                    prop = prop.replace(/([A-Z])/g,'-$1').toLowerCase();                style = styles ? styles.getPropertyValue(prop) :null;}elseif (elem.currentStyle){                style = elem.currentStyle[prop];}}return style;},
解説

これはよくある、現在のスタイルの値を求める関数ですね。
こういう関数は、扱う場合非常に注意すべきことがあります。

  • getComputedStyle で取得したオブジェクトのプロパティにアクセスすると、それまでの DOM の変更が一気に計算される
  • DOM に変更がない場合(スタイルを再計算する必要がない場合)は、プロパティのアクセスは軽い

ということです。
つまり、 ComputedStyle のプロパティアクセスと DOM の変更が交互に来るような場合が最悪で、一気に DOM を変更したあと、一気に getComputedStyle をするというのが理想です。
これは、Firefox ではどの程度影響があるかは分かりません(実験したことがないので、今度実験してみます)が、Google ChromeSafari などWebKit 系のブラウザではかなり顕著です。
HatenaStar.js での使い方を見てみると

    getImgSrc:function(c,container){var sel = c.ImgSrcSelector;if (container){var cname = sel.replace(/\./,'');var span =new Ten.Element('span',{                className: cname});// DOM の変更            container.appendChild(span);// スタイルが再計算されるvar bgimage = Ten.Style.getElementStyle(span,'backgroundImage');// DOM の変更            container.removeChild(span);if (bgimage){var url = Ten.Style.scrapeURL(bgimage);if (url)return url;}}if (sel){var prop = Ten.Style.getGlobalStyle(sel,'backgroundImage');if (prop){var url = Ten.Style.scrapeURL(prop);if (url)return url;}}return c.ImgSrc;}

このような場合が、一番重いです。(少なくともWebKit 系では)

はてなスターでは何故これをやっているか

これは、ユーザーがスターや add ボタンの画像をカスタマイズ出来るようにするためで、ユーザーが設定した背景画像を取得しているんですね。
ただ、少なくとも「はてなブックマーク」に関しては、スターやボタンの画像をカスタマイズしている箇所はありません。

試しに

この getImgSrc が一切何もせずにデフォルトの画像を返すとどのくらい、速くなるかを実験してみます。

    getImgSrc:function(c,container){// 決めうち!return c.ImgSrc;var sel = c.ImgSrcSelector;if (container){var cname = sel.replace(/\./,'');var span =new Ten.Element('span',{                className: cname});              container.appendChild(span);var bgimage = Ten.Style.getElementStyle(span,'backgroundImage');            container.removeChild(span);if (bgimage){var url = Ten.Style.scrapeURL(bgimage);if (url)return url;}}if (sel){var prop = Ten.Style.getGlobalStyle(sel,'backgroundImage');if (prop){var url = Ten.Style.scrapeURL(prop);if (url)return url;}}return c.ImgSrc;}

結果が以下

200ms 速くなりました。

改善する

現状の「はてスタ」のカスタマイズ方法を変えるわけにはいかないだろうと思いますので、使う側で getImgSrc を上書きしてしまいましょう(「新はてブ」では「はてスタ」カスタマイズ出来ないので OK)
こんどは HatenaStar.js を使う側の Bookmark.js に以下の一行を追加します。

if (typeof Hatena.Star !='undefined'){// ロードするURLを差し替える    Hatena.Star.EntryLoader.loadEntries =function(){};    Hatena.Star.EntryLoader.getStarEntries = Hatena.Bookmark.Star.getStarEntries;// この行を追加!!    Hatena.Star.Button.getImgSrc =function(c){return c.ImgSrc};// load swf// Ten.DOM.addEventListener('onload', function() {//     Hatena.Bookmark.Star.loadStarLoaderBySwf();// });}

はてなブックマークに限らず、カスタマイズせずに HatenaStar.js を使う時はこれをしとくといいですね。

プロファイリング


最高新記録でました!

重い箇所4:HatenaStar.js 1945 行目

    getImage:function(container){var img =document.createElement('img');var src = Hatena.Star.Button.getImgSrc(Hatena.Star.Star,container);        img.src = src;         img.setAttribute('tabIndex', 0);        img.className ='hatena-star-star';with (img.style){            padding ='0';             border ='none';}return img;},
うーん

これも 1738 行目 createButton と同じFirefox の img.src 重い問題ですね。僕の知ってる範囲では対処がありません><

重い箇所5: HatenaStar.js 348 行目

    getElementsByTagAndClassName:function(tagName, className,parent){if (typeof(parent) =='undefined')parent =document;if (!tagName)return Ten.DOM.getElementsByClassName(className,parent);var children =parent.getElementsByTagName(tagName);if (className){var elements =[];for (var i = 0; i < children.length; i++){var child = children[i];if (Ten.DOM.hasClassName(child, className)){                    elements.push(child);}}return elements;}else{return children;}},
解説

これは、タグの名前とクラス名から要素を取得する関数ですね。
とりあえず、パッと見て重そうなところは

  • XPath か SelectorsAPI が使える場合は使ったほうがいい
  • 今時のブラウザは getElementsByClassName はネイティブで実装されているので、あればそっち使ったほうがいい
  • for 文の終了判定 children.length (live な NodeList の length)は、毎回 DOM アクセスが発生する(外せば、ループが倍速で回る)

って感じですかね。

実際にはてブではどういう引数で使っているか「統計」を取る

こういうときは、Firebug の console.count を使います。

    getElementsByTagAndClassName:function(tagName, className,parent){// こんな感じで仕込んでおく        console.count(tagName +'.' + className);if (typeof(parent) =='undefined')parent =document;if (!tagName)return Ten.DOM.getElementsByClassName(className,parent);var children =parent.getElementsByTagName(tagName);if (className){var elements =[];for (var i = 0; i < children.length; i++){var child = children[i];if (Ten.DOM.hasClassName(child, className)){                    elements.push(child);}}return elements;}else{return children;}},

で、リロードすると

こんな感じの統計が取れます。

統計を見ると

タグ名とクラス名両方指定されることしかないようですね。もし、タグ名しか指定されないとか、クラス名しか指定されないとかだったら、ショートカットできるかと思ったのですが、まあ、セオリー通りXPath を使いましょう。

改善してみる

まずは、ブラウザ判定のところに以下を追加します。

/* Ten.Browser */Ten.Browser ={    supportsXPath: !!document.evaluate,// 追加!    supportsSelectorsAPI: !!document.querySelectorAll,    supportsGetElementsByClassName: !!document.getElementsByClassName,    isIE: navigator.userAgent.indexOf('MSIE') != -1,    isMozilla: navigator.userAgent.indexOf('Mozilla') != -1 && !/compatible|WebKit/.test(navigator.userAgent),    isOpera: !!window.opera,    isSafari: navigator.userAgent.indexOf('WebKit') != -1,    version:{        string: (/(?:Firefox\/|MSIE |Opera\/|Version\/)([\d.]+)/.exec(navigator.userAgent) ||[]).pop(),        valueOf:function(){return parseFloat(this.string)},        toString:function(){returnthis.string}}};

以下のような感じ

    getElementsByTagAndClassName:function(tagName, className,parent){if (typeof(parent) =='undefined')parent =document;if (!tagName){// ネイティブの getElementsByClassName があれば使うif (Ten.Browser.supportsGetElementsByClassName){returnparent.getElementsByClassName(className);}else{return Ten.DOM.getElementsByClassName(className,parent);}}// Selectors API が最速if (Ten.Browser.supportsSelectorsAPI){returnparent.querySelectorAll(tagName +'.' + className);}// XPath は次点elseif (Ten.Browser.supportsXPath){var result =document.evaluate("descendant::" + tagName +"[@class=" + className +" or contains(concat(' ', @class, ' '), ' " + className +" ')]",parent,null, 7,null);var elements =[];for (var i = 0, l = result.snapshotLength; i < l; i ++){                elements.push(result.snapshotItem(i));}return elements;}var children =parent.getElementsByTagName(tagName);if (className){var elements =[];// children.length の参照回数を減らすfor (var i = 0, l = children.length; i < l; i++){var child = children[i];if (Ten.DOM.hasClassName(child, className)){                    elements.push(child);}}return elements;}else{return children;}
プロファイリング


前の結果は hasClassName を含んでいるので実際には 10ms ほど速くなりました。
とは言え、雀の涙ですね。やはりXPath 式が複雑になってしまうためではないでしょうか。
Firefox 3.1 では SelectorsAPI を使えるようになるので、上のようにしておくと後々速くなることが期待できます。
また、前の章でカットした getImgSrc からは getElementsBySelector が呼ばれる可能性があるので、これも SelectorsAPI を使うようにしたほうがいいでしょう。(ここではやりません)

重い箇所6: HatenaStar.js 1031 行目

    getElementPosition:function(e){var pos ={x:0, y:0};if (document.documentElement.getBoundingClientRect){// IEvar box = e.getBoundingClientRect();var owner = e.ownerDocument;            pos.x = box.left + Math.max(owner.documentElement.scrollLeft, owner.body.scrollLeft) - 2;             pos.y = box.top  + Math.max(owner.documentElement.scrollTop,  owner.body.scrollTop) - 2}elseif(document.getBoxObjectFor){//Firefox            pos.x =document.getBoxObjectFor(e).x;            pos.y =document.getBoxObjectFor(e).y;}else{do{                 pos.x += e.offsetLeft;                pos.y += e.offsetTop;}while (e = e.offsetParent);}return pos;},
解説

これは、一回しか呼ばれていないのに 40ms とか食っていますね。

どこから呼ばれたのか調べる

Error().stackを使います

    getElementPosition:function(e){// これ!        console.log(Error().stack);var pos ={x:0, y:0};if (document.documentElement.getBoundingClientRect){// IEvar box = e.getBoundingClientRect();var owner = e.ownerDocument;            pos.x = box.left + Math.max(owner.documentElement.scrollLeft, owner.body.scrollLeft) - 2;             pos.y = box.top  + Math.max(owner.documentElement.scrollTop,  owner.body.scrollTop) - 2}elseif(document.getBoxObjectFor){//Firefox            pos.x =document.getBoxObjectFor(e).x;            pos.y =document.getBoxObjectFor(e).y;}else{do{                 pos.x += e.offsetLeft;                pos.y += e.offsetTop;}while (e = e.offsetParent);}return pos;},

こんな感じでスタックトレースが見れます。

Bookmark.js の 3580 行目から呼ばれているらしいです。

    fixedPosition:function(){var pos = Ten.Geometry.getElementPosition(this.form);var w = Ten.Geometry.getWindowSize();//this.layer.div.style.right = w.w - pos.x - this.form.offsetWidth + 'px';this.layer.div.style.right ='15px';this.layer.div.style.top = pos.y -this.form.offsetHeight - 30 +'px';//this.layer.moveTo(0, pos.y + this.form.offsetHeight);},

検索のポップアップの位置決めみたいですね。

改善してみる

document.getBoxObjectFor が二回も呼ばれている&使う側では y しか見ないので、以下のように改良してみます。

    getElementPosition:function(e){var pos ={x:0, y:0};if (document.documentElement.getBoundingClientRect){// IEvar box = e.getBoundingClientRect();var owner = e.ownerDocument;            pos.x = box.left + Math.max(owner.documentElement.scrollLeft, owner.body.scrollLeft) - 2;             pos.y = box.top  + Math.max(owner.documentElement.scrollTop,  owner.body.scrollTop) - 2}elseif(document.getBoxObjectFor){//Firefox// そのまま返すreturndocument.getBoxObjectFor(e);}else{do{                 pos.x += e.offsetLeft;                pos.y += e.offsetTop;}while (e = e.offsetParent);}return pos;},
プロファイリング


全然変わってません><もとにもどしておきます。

他は

目立って、ネックになっている箇所はなさそうですね。

ここまでの diff

HatenaStar.js
--- HatenaStar.js.org2008-11-04 18:20:37.000000000 +0900+++ HatenaStar.js2008-11-27 01:48:54.000000000 +0900@@ -347,11 +347,39 @@ Ten.DOM = new Ten.Class({     getElementsByTagAndClassName: function(tagName, className, parent) {         if (typeof(parent) == 'undefined') parent = document;-        if (!tagName) return Ten.DOM.getElementsByClassName(className, parent);++        if (!tagName) {++            // ネイティブの getElementsByClassName があれば使う+            if (Ten.Browser.supportsGetElementsByClassName) {+                return parent.getElementsByClassName(className);+            }+            else {+                return Ten.DOM.getElementsByClassName(className, parent);+            }+        }++        // Selectors API が最速+        if (Ten.Browser.supportsSelectorsAPI) {+            return parent.querySelectorAll(tagName + '.' + className);+        }++        // XPath は次点+        else if (Ten.Browser.supportsXPath) {+            var result = document.evaluate("descendant::" + tagName + "[@class=" + className + " or contains(concat(' ', @class, ' '), ' " + className + " ')]", parent, null, 7, null);+            var elements = [];+            for (var i = 0, l = result.snapshotLength; i < l; i ++) {+                elements.push(result.snapshotItem(i));+            }+            return elements;+        }+         var children = parent.getElementsByTagName(tagName);         if (className) {              var elements = [];-            for (var i = 0; i < children.length; i++) {++            // children.length の参照回数を減らす+            for (var i = 0, l = children.length; i < l; i++) {                 var child = children[i];                 if (Ten.DOM.hasClassName(child, className)) {                     elements.push(child);@@ -1029,6 +1057,7 @@         }     },     getElementPosition: function(e) {+        console.log(Error().stack);         var pos = {x:0, y:0};         if (document.documentElement.getBoundingClientRect) { // IE              var box = e.getBoundingClientRect();@@ -1157,6 +1186,9 @@  /* Ten.Browser */ Ten.Browser = {+    supportsXPath: !!document.evaluate,+    supportsSelectorsAPI: !!document.querySelectorAll,+    supportsGetElementsByClassName: !!document.getElementsByClassName,     isIE: navigator.userAgent.indexOf('MSIE') != -1,     isMozilla: navigator.userAgent.indexOf('Mozilla') != -1 && !/compatible|WebKit/.test(navigator.userAgent),     isOpera: !!window.opera,@@ -1376,6 +1408,28 @@         var isIE = navigator.userAgent.indexOf('MSIE') != -1;         var texts = [];         var pos = 0;++        // XPath をサポートしていたら+        if (Ten.Browser.supportsXPath) {++            // XPath で全てのテキストノードを取得する+            var result = document.evaluate('descendant::text()', document.body, null, 7, null);++            // テキストノードの走査+            for (var i = 0; i < result.snapshotLength; i ++) {+                var node = result.snapshotItem(i);+                c.textNodes.push(node);+                c.textNodePositions.push(pos);+                pos += node.length;+            }++            // textContent は一発で +            c.documentText = document.body.textContent || document.body.innerText;+            c.loaded = true;++            return;+        }+         (function(node, parent) {             if (isIE && parent && parent != node.parentNode) return;             if (node.nodeType == 3) {
Bookmark.js
--- Bookmark.js.org2008-11-26 19:35:06.000000000 +0900+++ Bookmark.js2008-11-27 00:33:45.000000000 +0900@@ -4867,6 +4867,9 @@     Hatena.Star.EntryLoader.loadEntries = function() {};     Hatena.Star.EntryLoader.getStarEntries = Hatena.Bookmark.Star.getStarEntries;+    // この行を追加+    Hatena.Star.Button.getImgSrc = function(c) { return c.ImgSrc };+     // load swf     // Ten.DOM.addEventListener('onload', function() {     //     Hatena.Bookmark.Star.loadStarLoaderBySwf();

まとめ

結局、一番効果があったのは、以下の2つでした。

  • makeTextNodes 関数(全 TextNode の走査)にXPath を使う
  • カスタマイズしない場合は Hatena.Star.Button.getImgSrc を上書きする

これで倍近く速くなりました。
結局、Firefox の場合は img.src 遅い問題が一番のネックになりますね。

感想

はてな」のJavaScript を触ってみた感想ですが、本当にしっかりと書けているなあと思いました。
僕の挙げたような高速化案はどれも、細かなブラウザ判定が必要な箇所ばかりで将来の「保守性」を犠牲にしてしまう可能性もあります。
きっと、「はてな」ではそのように判断して今のコードになったのではないでしょうか。

プロフィール
id:amachangid:amachangはてなブログPro

プログラミング、起業などについて書いているプログラマーのブログです😚

検索

引用をストックしました

引用するにはまずログインしてください

引用をストックできませんでした。再度お試しください

限定公開記事のため引用できません。

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

[8]ページ先頭

©2009-2025 Movatter.jp