jQuery.extend マニアックス

jQuery の extend メソッドは便利なんだが複雑で忘れてしまいがちなのでメモしておく。

jQuery.extend の呼び出しパターンは次の4通り。

  1. $.extend([deep,] target, obj1, [obj2, [obj3, ...]])
  2. $.extend([deep,] obj)
  3. $.fn.extend([deep,] obj)
  4. $(...).extend([deep,] obj)

全てのパターンで第一引数に [deep,] がある。これはオプションの引数で true を指定するとディープコピーしてくれる。

以下では分かりやすくするために deep オプションは省略した一覧を掲載する。

  1. $.extend(target, obj1, [obj2, ...]])
  2. $.extend(obj)
  3. $.fn.extend(obj)
  4. $(...).extend(obj)

だいぶシンプルになった。各パターンを見ていく。

$.extend(target, obj1, [obj2, ...]])

まずは1つ目のパターン。

$.extend(target, obj1, [obj2, ...]])

targetobj1 を上書きコピーし、書き換えられた target を返す。

以下は簡単な使用例。

>>> a = {a: 3, b: 4}
Object { a=3, b=4}
>>> $.extend(a, {c: 99})
Object { a=3, b=4, c=99}
>>> a
Object { a=3, b=4, c=99}

obj1 は複数個指定できる。複数個が必要あるのかと思うかもしれないが、継承もどきを実現するときに楽に書ける。

// 継承元クラス
function B() { }
B.prototype = {
    a: function() { }.
    b: function() { }
}

// 派生クラス
function D() { }
D.prototype = $.extend({}, B.prototype, {
    c: function() { }.
    d: function() { }
});

もし、ここで

D.prototype = $.extend(B.prototype, {
    //....
);

とやってしまうと、B.prototype が書き換えられてしまう。

そこで、一旦、空オブジェクトに B.prototype をコピーしておいて、D に実装したいメソッドを上書きコピーしてあげている。

ちなみに

D.prototype = $.extend({}, B.prototype, { ... });

は、次のように書いてもよい書くと同じように動くように見えるが

D.prototype = $.extend({ ... }, B.prototype);

こうすると、B と D に同じメソッド名を作成しようとしたときに、B のものが優先されてしまうので注意が必要だ。

target を省略した extend

次は、target を省略した extend の呼び出しパターン3つを見る。

  1. $.extend(obj)
  2. $.fn.extend(obj)
  3. $(...).extend(obj)

target を省略して obj が1つしかない場合には、extend を呼び出したときの this が target として解釈される。

$.extend(obj)

$.extend(obj) は jQuery 自身を obj で拡張する。

例えば、

$.extend({
    min: function(a, b) { return a < b ? a : b; }
});

とすると、$.min(3, 5); で小さいほうの値を取得できる(引用元:jQuery.extend(object) - jQuery 日本語リファレンス)。

jQuery クラスに static メソッドを追加するようなイメージだ。

$.fn.extend(obj)

$.fnjQuery.prototype のこと。つまり、$.fn.extend(obj) を利用すると jQuery オブジェクトにメソッドを追加できる。

たとえば、

$.fn.extend({
    log: function() { console.log(this); return this; }
});

とすることで、log メソッドを追加できる。

例えばこんな風にして使う。

$("div")
    .log()
    .each(function(){
        // ...
    })

メソッドチェーンの途中に突っ込んでログ出力できて便利!

$.fn.extend は各種 jQuery プラグインの実装で頻繁に利用されている。

$(...).extend(obj)

実際に利用されてるのは見たことないし、公式ドキュメントにも書いていないんだけど、これが意外に便利なんじゃないかというのが今回の本題。本題までが長かった…。

$(...).extend(obj) を使うと、extend されるのは jQuery オブジェクト自身。jQuery オブジェクトにメソッドを追加できる。

例えば「フォームの要素を enable/disable する」という処理がソースのあちらこちらに存在しているする。

$("<form>").submit(function(){
    // form 配下のコントロールを disable にする
    $(this).find(":enabled").attr("disabled", "disabled");

    var self = this;
    $.post('url', { ... }, function(){
        // POST が完了すると、コントロールを enable にする
        $(self).find(":disabled").attr("disabled", "");

        // ...
    }, "json");
    return false;
});

この例ではまだ一箇所だが、find(":enabled").attr("disabled", "disabled") のようなコードが色んな場所にあると、ソースが煩雑になる。

そこで、次のようにしてみよう。

var enable_mixin = {
    enableAll: function(){
        this.find(":enabled").attr("disabled", "");
        return this;
    },
    disableAll: function(){
        this.find(":enabled").attr("disabled", "disabled");
        return this;
    }
};

$("<form>").submit(function(){
    // form 配下のコントロールを disable にする
    $(this).extend(enable_mixin).disableAll();

    var self = this;
    $.post('url', { ... }, function(){
        // POST が完了すると、コントロールを enable にする
        $(self).extend(enable_mixin).enableAll();

        // ...
    }, "json");
    return false;
});

ちょっとしたメソッド集 enable_mixin を作成しておく。そして、メソッドチェーンの中で、extend を使って動的にメソッドを追加し、そのメソッドをすぐさま呼び出している。

もちろん、$.fn.extend を使えば全ての jQuery オブジェクトにメソッドを追加できるのだが、全体で利用するほどの汎用性はないようなちょっとした処理を、その場で気楽にミックスインして使えるのがうれしい。

おわり

jQuery.extend は複雑である。