jQuery の関数をオーバーライドする方法

 今回は、jQuery を使って$('#hoge').hoge()とした時に、元のhogeの機能に加えて追加のコードが走るようにしたいですね、というお話です。

普通の JavaScript で関数をオーバーライドする方法

 こちらは JSer なら常識ですね。

var orgHoge = window.hoge;
window.hoge = function() {
    // TODO: 事前の追加のコード
    var result = orgHoge.apply(this, arguments);
    // TODO: 事後の追加のコード
    return result;
};

 なお、このコードを見て「処理が足りてないじゃないか」と言える方は、以降の話は全く必要ないかと思います。

jQuery の関数をオーバーライドする方法

 では、jQuery$('#hoge').hoge()とした時のhogeはどこで定義されているかというと、$.fn.hogeです。これまた jQueryer にとっては常識ですね。

 同様に、オーバーライドしてみましょう:

var orgHoge = $.fn.hoge;
$.fn.hoge = function() {
    // TODO: 事前の追加のコード
    var result = orgHoge.apply(this, arguments);
    // TODO: 事後の追加のコード
    return result;
};

 簡単。試しに jQuery Activity Indicator を使い、具体例を作ってみました。

 こちらがオーバーライド前。
 真面目に書くと面倒なので必要最低限ですが、ON ボタンを押すと枠の中がくるくる回って、OFF ボタンを押すと消えます。

<!DOCTYPE html>
<html>
    <head>
       <script type="text/javascript" src="http://code.jquery.com/jquery-2.1.1.min.js"></script>
       <script src="jquery.activity-indicator-1.0.0.js"></script>
       <style>
        #test {
            width: 100px;
            height: 100px;
            border: solid red 0.25em;
        }
        </style>
   </head>
    <body>
        <div>
            <button onclick="$('#test').activity()">ON</button>&emsp;<button onclick="$('#test').activity(false)">OFF</button>
        </div>
        <div id="test"></div>
    </body>
</html>

 次に、くるくるが回り始めたら枠線を灰色にして、止まったら赤に戻すようにしてみましょう。

<script>
$(function(){
   var activity = $.fn.activity;
   $.fn.activity = function(){
       if (arguments[0] === false) {
           $(this).css('borderColor', 'red');
       }
       else{
           $(this).css('borderColor', 'gray');
       }
       return activity.apply(this, arguments);
   };
});
</script>

 あれ……くるくるが出ない!!

どうして出ないの?

 試しにスクリプトデバッグしてみると、次のようなメッセージが出ます:

transform 属性のパース中に予期せぬ値 translate(NaN,NaN) が見つかりました。
el.setAttributeNS(null, k, v);

 ……よくわからん。

 とりあえず Activity Indicator のソースをよく見てみると、次のような部分がある事に気付きます。

opts = $.extend({color: $this.css('color')}, $.fn.activity.defaults, opts);

 どうやら、オプションのデフォルト値を$.fn.activity.defaultsに持っていて、ユーザーが指定していないオプションがある場合にはそこから値を取っているようです。
 オーバーライドした$.fn.activitydefaultsプロパティを持っていないので、変なところにundefinedが入って先ほどのエラーが出たものと思われます。

 なので、正しくはこうやって、各種プロパティをオーバーライドした関数に付け替えてやらねばなりません:

<script>
$(function(){
   var activity = $.fn.activity;
   $.fn.activity = $.extend(function(){
       if (arguments[0] === false) {
           $(this).css('borderColor', 'red');
       }
       else{
           $(this).css('borderColor', 'gray');
       }
       return activity.apply(this, arguments);
   }, activity);
});
</script>

 これで、想定通りに動くようになりました。

 このように関数にデフォルトプロパティを持たせる方法は、jQuery プラグインでは多用されています。
 まず、こんなコードを書かなければならない事を疑うべきだとは思いますが、万が一使いたくなった場合*1には憶えておいて損はないでしょう。

jQuery 以外の JavaScript に戻って

 本当は、こういった「関数に動作設定用プロパティを追加してある」というやり方は、jQuery に限らず出てきます。
 ですので最初の、jQuery ではない JavaScript のコードでも、同様の処理をしてやらねばいけませんね(「処理が足りてないじゃないか」というのはそれの事)。

*1:GreaseMonkey で既存サイトの動作を変更したい場合に、全く欲しくならないとは言い切れない……くらいですかね。