2013/09/27更新

[JS] $(elm).on(event,fn)と$(elm).on(event,selector,fn)は動きが違うじゃないですか

このエントリーをはてなブックマークに追加      

こんにちは、です。

$('#page .btn').on('click', fn)と$('#page').on('click', '.btn', fn)は動きが違うってこと知らなかった。。


ということで、このブログでは、上記2つの使い方の違いを纏めたいと思います。

画像



2つの違いについて

参考URL:http://api.jquery.com/on/

2つの違いはイベントの扱い方が違うようです。
「$('#page .btn').on('click', fn)」の場合には、イベントが要素($('#page .btn'))に対して、 イベントと直接バインドします。
「$('#page').on('click', '.btn', fn)」の場合には、デリゲートとして扱うようです。
以下では、それぞれの内容を書きます。



イベントのDirect Bind

onメソッドのselectorを省略した場合には、要素に対して直接イベントが張り付きます。
上野参考URLからの引用です。
If selector is omitted or is null, the event handler is referred to as direct or directly-bound. The handler is called every time an event occurs on the selected elements, whether it occurs directly on the element or bubbles from a descendant (inner) element.
(略訳)
もし"selector"引数が省略された(もしくはnullの)場合、イベントハンドラーは直接バインドする対象として参照されます。 イベントハンドラーは、対象要素に対するイベント(またはイベントバブリングで関連する)毎に毎回呼び出されます。


この動きは、jQueryなどを使わない場合のイベントバインディングの動きと同じなので、分かりやすいですね。
自分は、onメソッドにはこの動きのみだと勘違いしていました。

例えば、以下のHTMLに対して、
<table id="table1">
    <tr>
        <td></td>
        <td></td>
    </tr>
    <tr>
        <td></td>
        <td></td>
    </tr>
</table>
以下のように、jQueryでイベントバインドする場合、
$('#table1 td).on('click', function () {
    alert('tdがclickされた');
});
4つのtd要素に対して、それぞれclickイベントがバインドされます。
これが、"selector"引数を省略した場合の動きです。



デリゲートとして動作する

"selector"引数を指定した場合には、上記の動きとは異なる動きをします。
上記のURLから引用した内容が以下です。
When a selector is provided, the event handler is referred to as delegated. The handler is not called when the event occurs directly on the bound element, but only for descendants (inner elements) that match the selector. jQuery bubbles the event from the event target up to the element where the handler is attached (i.e., innermost to outermost element) and runs the handler for any elements along that path matching the selector.
(略訳)
"selector"引数が指定された場合、イベントハンドラーはDelegateとして扱われます。 イベントハンドラーがバインドされた要素($('#page')の部分)に対するイベントに反応するのではなく、 イベントハンドラーがバインドされた要素の子孫要素で"selector"引数にマッチする要素がある場合に、イベントハンドラーが動作します。 イベントハンドラーは、"selector"要素にマッチする要素に対して動作します。


ちょっと分かりづらいかもしれません。
例えば、以下のHTMLがある場合に-、
<table id="table1">
    <tr>
        <td></td>
        <td></td>
    </tr>
    <tr>
        <td></td>
        <td></td>
    </tr>
</table>
以下のようにイベントバインドした場合、
$('#table1').on('click', 'td', function () {
    alert('tdがclickされた');
});
イベントハンドラーは$('#table1')でデリゲートイベントとして登録されて、 tdがクリックされた場合にイベントが発火します。 イベントは#table1に対して1つのみ登録されるので、上記の4つのtdにイベントバインドされる挙動とは異なります。

ここまでの内容だと、"selector"要素をする方法でもしない方法でも結果的に動作が同じなのでどっちを使っても良いのですが、 以下の点に違いがあるので、使い分けをする必要があります。



"selector"引数を指定するメリット①

"selector"引数を指定するメリットの1つ目は、将来追加される要素に対してもイベントを規定できる点です。
例えば、以下のHTMLがある場合に、
<table id="table1">
    <tr>
        <td></td>
        <td></td>
    </tr>
    <tr>
        <td></td>
        <td></td>
    </tr>
</table>
jQueryのonメソッドの"selector"引数ありで、イベントを定義します。
$('#table1').on('click', 'td', function () {
    alert('tdがclickされた');
});
この場合、上記のonメソッド実行以降に以下のようにテーブル要素を追加した場合には、 その追加したtdでも上記onメソッドで定義したイベントが発生するようになります。
$('#table1').append('<tr><td>aaa</td><td>bbb</td></tr>');
これは、例えばBackbone.jsのようなMVCを実装する際に、 動的にHTMLを追加したり削除したりする実装をするにあたりすごく便利です。

jQuery1.7あたりで削除されたliveメソッドの後継者のようです。



パフォーマンス向上する場合も

jQueryのAPI説明ページでは、以下のように解説されています。
In addition to their ability to handle events on descendant elements not yet created, another advantage of delegated events is their potential for much lower overhead when many elements must be monitored. On a data table with 1,000 rows in its tbody, this example attaches a handler to 1,000 elements: [省略] A delegated-events approach attaches an event handler to only one element.
(略訳)
Delegateイベントのメリットは、まだ存在しない要素に大してもイベントを扱うことが出来るのに加え、 イベント対象が多い場合に、イベントを直接バインドする方式に比べて、低いコストでイベントハンドリングが出来ます。 例えばテーブルに1000行のデータがある場合に、イベント直接バインドでは1000個のイベントをbindしますが、 Delegateイベントの場合、1つのみbindします。


例えば、以下のようにイベントをバインドした場合、
$('#table1 td').on('click', function () {
    alert('tdがclickされた');
});
対象のtd要素が1000個あれば、1000個のイベントがjQueryによって管理されることになります。 これはたくさんのイベント数を扱うという意味で、パフォーマンス劣化の原因になる可能性があります。

上記と異なり以下のように実装すれば、
$('#table1').on('click', 'tr', function () {
    alert('aaa');
});
jQuery内で扱うイベントは1つのみとなり、パフォーマンス向上に寄与する可能性があります。



最後に

jQueryを1年くらい集中的に使ってましたが、この機能は最近まで知りませんでしたw。 jQuery使っている方なら、当然の機能ということなのかもですが。。
あと今回の内容でjQueryのコードリーディングも行いましたが、色々と勉強になりました。 コードを読むって良いですね。

最後までご覧頂きましてありがとうございました。





こんな記事もいかがですか?

RSS画像

もしご興味をお持ち頂けましたら、ぜひRSSへの登録をお願い致します。