JavaScript コードの最適化 (Optimizing JavaScript code)

JavaScrip の実行効率を上げる方法のいくつかの例が "Let’s make the web faster – Google Code" (日本語訳:JavaScriptの最適化について、code.google.comの記事の適当訳 – それ図解で。・・・tohokuaikiのチラシの裏)に掲載されていた。

文字列の結合

+ を使った文字列結合よりも、配列要素にして join を使った方が効率的(IE6,7 でのガベージコレクションに影響する)。
速度(Firefox3.5の場合)は join よりも + を使った方が速かった。

文字列結合のProfile

$(function() {
    $('#run').click(function() {
        console.profile;
        addStr();
        joinStr();
        console.profileEnd();
    });
});

var fibonacci = function(n) {
    if (n === 0) {
        return n;
    } else {
        return fibonacci(n - 1);
    }
}

var addStr = function() {
    var fibonacciStr = 'First 20 Fibonacci Numbers';
    for (var i = 0; i < 20; i++) {
        fibonacciStr += i + ' = ' + fibonacci(i) + '';
    }
}
var joinStr = function() {
    var strBuilder = ['First 20 fibonacci numbers:'];
    for (var i = 0; i < 20; i++) {
        strBuilder.push(i, ' = ', fibonacci(i));
    }
    var fibonacciStr = strBuilder.join('');
}

クラスメソッドの定義

prototype を使うと、生成するインスタンスの数に関係なく1つのプロトタイプ関数が作られるだけで済む。
インスタンス毎にクロージャが作られることもない。

デモ:JavaScript クラスメソッドの定義

var baz = {};
baz.Bar = function() {
    this.foo = function() {
        return 'Hello from BAZ.';
    };
};

var moo = {};
moo.Bar = function() {
};
moo.Bar.prototype.foo = function() {
    return 'Hello from MOO.';
};

$(function() {
    $('#run').click(function() {
        var b = new baz.Bar();
        var m = new moo.Bar();
        $('#debug').html(
            "b.toSource() =&gt; " + b.toSource() + "\n" +
            "m.toSource() =&gt; " + m.toSource() + "\n" +
            "b.foo() =&gt; " + b.foo() + "\n" +
            "m.foo() =&gt; " + m.foo()
        );
    });
});

実行結果

b.toSource() => ({foo:(function () {return "Hello from BAZ.";})})
m.toSource() => ({})
b.foo() => Hello from BAZ.
m.foo() => Hello from MOO.

インスタンス変数の初期化

prototype でインスタンス変数を宣言・初期化すると、コンストラクタが呼ばれる度に初期化処理コードが実行されることを防ぐ。

デモ:JavaScript インスタンス変数の初期化

var foo = {};
foo.Bar = function() {
    this.prop1_ = 4;
    this.prop2_ = true;
    this.prop3_ = [];
    this.prop4_ = 'blah';
};

var hoge = {};
hoge.Bar = function() {
    this.prop3_ = [];
};
hoge.Bar.prototype.prop1_ = 4;
hoge.Bar.prototype.prop2_ = true;
hoge.Bar.prototype.prop4_ = 'blah';

$(function() {
    $('#run').click(function() {
        var f = new foo.Bar();
        var h = new hoge.Bar();
        $('#debug').html(
            "f.toSource() =&gt; " + f.toSource() + "\n" +
            "h.toSource() =&gt; " + h.toSource() + "\n" +
            "f.prop1 =&gt; " + f.prop1_ + "\n" +
            "h.prop1 =&gt; " + h.prop1_ + "\n" +
            "f.prop2 =&gt; " + f.prop2_ + "\n" +
            "h.prop2 =&gt; " + h.prop2_ + "\n" +
            "f.prop3 =&gt; " + f.prop3_.toSource() + "\n" +
            "h.prop3 =&gt; " + h.prop3_.toSource() + "\n" +
            "f.prop4 =&gt; " + f.prop4_ + "\n" +
            "h.prop4 =&gt; " + h.prop4_
        );
    });
});

実行結果

f.toSource() => ({prop1_:4, prop2_:true, prop3_:[], prop4_:"blah"})
h.toSource() => ({prop3_:[]})
f.prop1 => 4
h.prop1 => 4
f.prop2 => true
h.prop2 => true
f.prop3 => []
h.prop3 => []
f.prop4 => blah
h.prop4 => blah

循環参照について(イベントハンドラの設置)

デモ:JavaScript イベントハンドラの循環参照

$(function() {
    var menu = document.getElementById('my-menu');
    $('#run').click(function() {
        attachEvt(menu);
    });
});

var attachEvt = function(element) {
    var mouseHandler = function() {
        /* whatever */
        $('#debug').html('done.');
    };
    element.addEventListener('mouseover', mouseHandler, false);
};

循環参照

attachEvt の中に mouseHandler をネストさせている。
これは、ハンドラ(mouseHandler) が呼び出し元の attachEvt のスコープ内に閉じ込められることを意味する。
したがって、ハンドラは element への参照を保持し続ける。
elementmenu への参照を保持し、menudiv#my-menu への参照を保持する。
さらに div#my-menu はハンドラへの参照を保持している。
これを循環参照(circular reference)という。
参考:Fabulous Adventures In Coding : What are closures?
   element.addEventListener – MDC

«
»