よくある解説
- bindはajax等で変更した要素にはイベントが動かない
- liveはajax等で変更した要素にもイベントが動く
問題
<table>
<tr>
<td>ほげ</td>
</tr>
</table>
$('table').bind('click', function(){ console.log('bind-table') });
$('tr').live('click', function(){ console.log('live-tr') });
$('td').live('click', function(){ console.log('live-td') });
という状態の時、td
をクリックした際にコンソールにどういう順番で出力されるか。
回答、、、の前に
javascriptのイベントは本来、外側から内側(tableからtd)に向かい(キャプチャーフェーズ)、対象の要素まで到達すると、今度は外側に向かいます(バブリングフェーズ)。ところが、jqueryが出た当時のIEにはキャプチャーフェーズがなく、バブリングフェーズしかなかった、からなのか、jqueryのイベント動作もバブリングフェーズだけになりました。なので、jqueryで捕捉できるイベントは、内側から外側に向けてのバブリングフェーズだけです。
回答
bind-table
live-td
live-tr
の順に出力されます。
tdが最初じゃないの?
- 内側からイベントが動くんじゃないの?
ここで最初の話に戻りますが、なぜliveでイベントをセットするとajaxで書き換えた要素に対してもイベントが動き、bindでイベントセットするとajaxで書き換えた要素に対してイベントが動かないか。という話なのですが。
実はliveはbindとは違い最初のセレクタで一致した要素に対して イベントセットしてません。
$('#id').bind(); //#idにイベントをセット
$('#id').live(); //#idにはイベントをセットしてない
じゃあどこに?
- デフォルトでは
document
- 正確には
context
$(selector, context);//←これ
context
にエレメント以外を渡すか何も渡さないとdocumentがセットされます。
(セレクタやjqueryオブジェクトを渡してもcontextは変わらない)
$(selector).context;//document
$(selector, '#id').context;//document
$(selector, $('#id')).context;//document
$(selector, $('#id')[0]).context;//#id
つまり、
table
tr
td
にイベントをセットしたとみせかけて、実際には
table
document(tr)
document(td)
にイベントをセットしていたことになります。
document
はDOMのトップレベルオブジェクトですから、当然一番外側になり、イベント伝播は最後になります。また、セレクタ要素に対してはイベントセットしていないことになるので、ajaxで書き換えられようが、関係なくイベントが動くということになります。
あれ・・・? 最初のセレクタ無駄じゃない?
$(selector) //selector要素を走査
.bind(); //その走査した要素にイベントセット
$(selector) //selector要素を走査
.live(); //その走査した要素にイベントセットしない→走査する必要なくない?
無駄ですね。かつ、半強制的にdocumentに対してセットしているのもいかがなものか。
また、走査したものを使わないということは、Traversing系メソッドのチェーンに使いづらい(使えない)、という話にもなります。
そういうわけで、delegateが出来た
$('table').delegate('td', 'click', function(){
console.log("table - td");
});
delegateは(だいたい)デリゲートと読み、意味は「委譲」と訳されます。
上記でいうと、tdのイベントをtableに委譲しています。
イベント自体はtableに、発火点はtdにすることで、td(tr)の要素がajaxで書き換わっても、バブリングフェーズでイベントをキャッチするのはtableなので、イベントが動きます。また、table
を走査してtable
にイベントセットしているので、無駄になりません。また、このように委譲することで、tableの中にtdが大量にあっても、イベントセットはtableの1つだけになり、パフォーマンス的にも優れています。
ちなみに、
//こう書けば、
$(document).delegate('td', 'click', function(){
console.log("fire!");
});
//以下と同じことになります。
$('td').live('click', function(){
console.log("fire!");
});
イベントセット方法多すぎ!
- で、onが出来ました。
- 今まで書いてきたイベントセット方法は全てonで出来るようになります。
bind→on
$('td').bind('click', fn);
$('td').on('click', fn);
- ほぼ同じですね。新しく覚えなおすことはありませんし、bindと書くよりも、onと書いた方が短いので、onを使いましょう。
delegate→on
$('table').delegate('td', 'click', fn);
$('table').on('click', 'td', fn);
- 引数の順番が変わりました。
- 基本的には第1引数がイベント名、その後ろにセレクタがあればそのセレクタが発火点ということだけ覚えておけばほぼbindと同じですので、delegateの書式を覚えるよりも簡単です。なので、onを使いましょう。
live→on
$('table').live('click', fn);
$(document).on('click', 'table', fn);
- ある意味一番ややこしいですが、 liveがdocumentにイベントをセットしている ということさえ理解していれば、delegateと同じになります。また、イベントセット箇所が明示的になるのでonを使いましょう。
- というか、もう1.9以降、使用不可です。
以上を踏まえて
- 最初の問題を書き換えるとこうなります。
$('table').on('click',function(){
console.log('table');
});
$(document).on('click', 'tr', function(){
console.log('tr');
});
$(document).on('click', 'td', function(){
console.log('td');
});
あら不思議、どのように出力されるかは一目瞭然ですね。
ちなみに
- onが使えるようになってからは、bind、live、delegateはただの入り口です。
//一部抜粋
bind: function( types, data, fn ) {
return this.on( types, null, data, fn );
},
//liveは1.8.3まで
live: function( types, data, fn ) {
jQuery( this.context ).on( types, this.selector, data, fn );
return this;
},
delegate: function( selector, types, data, fn ) {
return this.on( types, selector, data, fn );
},
jquery界隈の人たちが、イベントセットするのに、on
を使えとよく言っている意味が分かったかと思います。
ちなみにちなみに
- clickもただの入り口です。というか見て分かる通り、だいたい入り口です。
jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " +
"mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
"change select submit keydown keypress keyup error contextmenu").split(" "), function( i, name ) {
// Handle event binding
jQuery.fn[ name ] = function( data, fn ) {
return arguments.length > 0 ?
this.on( name, null, data, fn ) :
this.trigger( name );
};
});
this.onの引数を見ると分かると思いますが、onが作られる前はbindが使われていました。
まとめ
- clickとbindはほぼ一緒。というか、呼び出し方が違うだけ。
- bindとliveは書式が同じだけど、イベントのセット先が違う。
- liveとdelegateは書式が異なっていて、委譲してる点は同じだけど、delegateには無駄な走査が発生しない。
- on以外はonの入り口。