2007年04月25日 12:00 [Edit]

Digest - 今日にでも使うべきJavaScriptの7つのテクニック

良質の記事だけに全訳したかったのだけど、時間もないので紹介と抄録。

サンプルコードは、適宜書き換えてあります。


1. Branch when possible - 分岐はなるはやで

これは実例を見た方が早いでしょう。クロスブラウザー対応のaddListener()を考える。機能だけを考えれば、以下でOK。

function addListener(el, type, fn) {
    if ( window.addEventListener ) {
        el.addEventListener(type, fn, false);
    } else if ( window.attachEvent ) {
        var f = function() {
            fn.call(el, window.event);
         };
         el.attachEvent('on'+type, f);
    } else {
         element['on'+type] = fn;
    }
};

でも、これだとaddListener()実行のたびに、ブラウザー判定の条件分岐する必要があります。百回使えば百回ブラウザー判定をしてしまうのです。

以下のようにかけば、条件分岐は最初の一回で済みます。

var addListener = (function() {
    if ( window.addEventListener ) {
        return function(el, type, fn) {
            el.addEventListener(type, fn, false);
        };
    } else if ( window.attachEvent ) {
        return function(el, type, fn) {
            var f = function() {
                fn.call(el, window.event);
            };
            el.attachEvent('on'+type, f);
        };
    } else {
        return function(el, type, fn) {
            el['on'+type] = fn;
        }
    }
})();

2. Make Flags - フラグを作っておこう

ここでのポイントは、!!expresssion。expressionの型が何であれブール値になります。確かに

function toBool(expression){ return expression ? true : false }

とやることも出来ますが、!!expresssionの方が簡潔です。

3. Make bridges - 橋渡しコードを用意しよう

これも実例で。

addListener(element, 'click', getUserNameById);
function getUserNameById(e) {
    var id = this.id;
    // idを処理
}

と書くと、ブラウザー依存コードが出来てしまいます。thisに何が入るかはブラウザーによって異なるからです。

以下のように橋渡しコードを書けば、それが解決します。

function getUserNameById(id) {
    // idを処理
}
function getUserNameByIdBridge (e) { // これで橋渡し
    getUserNameById(this.id);
}
addListener(element, 'click', getUserNameByIdBridge);

4. Try Event Delegation - イベント委譲を使ってみよう

<ul id="example">
    <li>foo</li>
    <li>bar</li>
    <li>baz</li>
    <li>thunk</li>
</ul>

のようなHTMLに、liをクリックした際に何かをしたいとします。この場合、liごとにイベントハンドラーを追加するより、親要素のulに以下のハンドラーを追加すればよいのです。

var element = document.getElementById('example');
addListener(element, 'click', handleClick);
function handleClick(e) {
    var element = e.target || e.srcElement; // これでelementはulの中のクリックされたli
    // elementを処理
}

5. Include methods with your getElementsByWhatever - getElementsByHogeHogeにメソッドを渡せるようにしておこう

getElementsByHogeHogge()という、DOMの中から規定を満たす要素だけ取り出すという関数はよく使うかと思います。実装はこんな感じ。

function getElementsBySelector(selector) {
    var elements = [];
    for ( ... ) {
        if ( ... ) elements[elements.length] = el;
    }
    return elements;
}

ここで、第二引数にコールバック関数を渡せるようにしておけば、さらに便利です。

function getElementsBySelector(selector, callback) {
    var elements = [];
    for ( ... ) {
        if ( ... ) {
            elements[elements.length] = el;
            callback(el);
        }
    }
    return elements;
}
getElementsBySelector('#example p a', function(el) {
    // 要素 el を処理
});

6. Encapsulate your code - コードをカプセル化しよう

これは、関数でスコープを作ろうということですね。

var a = 'foo';
var b = function() {
    // なんか処理
};
var c = {
    thunk: function() {
    },
    baz: [false, true, false]
};

と書くと変数a,b,cがWindowグローバルになってしまいますが、

(function() {
  var a = 'foo';
  var b = function() {
    // なんか処理
  };
  var c = {
    thunk: function() {
    },
    baz: [false, true, false]
  };
}();

とすれば、Windowグローバルな変数は全くありません。余談ですが、Google Analyticsのurchin.jsはこれを見習って欲しいと思います。

7. Reinvent the Wheel - 車輪を再発明しよう

そして一番感銘を受けたのが、ここです。ここだけは全訳します。

Digital Web Magazine - Seven JavaScript Techniques You Should Be Using Today
Often, when I sit down to prototype out code or build a widget, I’m almost positive someone has already accomplished what I’m about to attempt. But nevertheless, it’s worth my effort to see if I can do it better. Not every wheel is round. Nor does every round wheel have twenty-inch shiny spinners. Imagination with such a highly flexible language can take you a long way.
[コードを試し書きしたりウィジェットを作ったりしているとき、自分がやろうとしていることは別の誰かがすでにやっているだろうというのは想定の範囲です。それでも、もっとうまいやり方があるのか自分で試す価値はあります。世の車輪の全てが丸くてピカピカというわけではないのです。これほど柔軟な言語なのですから、想像力の果てまで行っていませんか?]

Perl Mongerの多くがJavaScriptにもハマる理由がこれですね。でもCPAN相当は欲しいなあ。function $(id){ return document.getElementById(id) }ぐらいならいくらでも再発明するけど、addLisetner()ぐらいでももうコピペがいやになるし....かといって、JSANはちょっとCPAN臭すぎるような気もするし....

Dan the Rhino Tamer


この記事へのトラックバックURL

この記事へのトラックバック
オライリー矢野様より献本御礼。いつもありがとうございます。 JavaScript &amp; DHTMLクックブック 第2版 Danny Goodman / 村上列訳 [原著:Javascript &amp; Dhtml Cookbook (2nd Ed.)] 初出2008.06.17; 販売開始まで更新 JavaScriptのクックブックとしては結...
怠惰が足りないが短気旺盛 - 書評 - JavaScript & DHTMLクックブック 第2版【404 Blog Not Found】at 2008年06月17日 17:17
404 Blog Not Found:Digest - 今日にでも使うべきJavaScriptの7つのテクニックで効率的なイベントリスナの追加方法を紹介しています しかしそのソースにはIEでremoveEventListenerできない欠点があるようです IEの方では関数(A)をローカル橋渡し関数(B)に渡してからリスナ
[Javascript]効率的なイベントリスナ【ざくろの日記 =NULL=】at 2008年06月14日 18:59
ちょっと風邪気味なので今日は家でおとなしく。 SvTRUE、PurePerl版 - D-6 [相変わらず根無し] sub SvTRUE{ my $x = shift; if (! defined $x) { return (); } if (!ref $x &amp;&amp; $x =~ /\D/) { return length($x) &gt; 1 ...
perl - 勝手に添削 - SvTRUEをPurePerlで【404 Blog Not Found】at 2007年12月07日 19:00
この記事へのコメント
karronoli様、
ほんとだ。直しました。ありがとうございます。この記事今でもよく参照されています。
Dan the Typo Generator
Posted by at 2008年06月07日 23:51
一年以上も前の記事なので何も返答なさそうですが
1のaddListenerは
element['on'+type] = fn;
じゃなくて
el['on'+type] = fn;
が正しいですよね?

-- まあ大抵上2つで足りる環境しか使わないで必要なさそうですが



Posted by karronoli at 2008年05月31日 18:11
こんなのものありました。
http://www.hyuki.com/yukiwiki/wiki.cgi?EfficientJavaScript
Posted by Weasel at 2007年04月26日 10:06
TAKESAKOさん、
さいです。コピペの位置が間違ってました。
(function(s){ document.write(s) })('Dan the Typo Generator')
Posted by at 2007年04月25日 15:01
3.の橋渡しコードで呼ばれる側はこうでしょうか。
function getUserNameById(id) {
// idを処理
}
Posted by TAKESAKO at 2007年04月25日 14:24
pさん、
ですね。function(){}()は駄目で(function(){})()です。
原典の間違いもそのまま引き継いじゃいました。直しておきました。
(function(s){ document.write(s) })('Dan the JavaScripter')
Posted by at 2007年04月25日 13:22
重箱の隅だけど6の2番目のコードは)が一個足りねっすよ
Posted by p at 2007年04月25日 13:14