jQuery.event あたりを読む
今日はいっぱい読んできたよ
- 読んだもの
- jQuery 1.3.2のイベント周りを中心で読みました。
- このエントリで使う表現
- l:xxxx ⇒ xxxx行目
- l:xxxx - yyyy ⇒ xxxx行目〜yyyy行目
- ////hoge ⇒ 僕が書いたコメント
主催者なのにぽかぽかした陽気で電車を寝過して遅刻しました、
ごめんなさいごめんなさい!お詫びにおせんべ買ってった。
id:seiunsky つないでくれてありがとうございます!
まずはよく使うメソッドのbindから(l:2895-2899)
bindは、bind( type, [data], fn )という真ん中がoptionalな引数になってます。
2895: bind: function( type, data, fn ) {//// 実際にユーザーが呼ぶ関数 2896: return type == "unload" ? this.one(type, data, fn) : this.each(function(){ //// 各jQueryオブジェクトに対してevent.add 2897: jQuery.event.add( this, type, fn || data, fn && data ); 2898: }); 2899: },
event typeが"unload"だったらjQuery.one(l:2901)に任せるということで、今回はより一般的な方で。
l:2897でjQuery.event.addを各jQueryオブジェクトに対して行っています。
ここでevent.addに渡してるのは、
- 第1引数にjQuery object
- 第2引数にevent type(bindの第一引数)
- 第3引数にhandler関数(bindが引数2つで呼ばれた時は2つ目、3つで呼ばれてれば3つ目)
- 第4引数にoptionalなdata(bindが引数3つで呼ばれた時の2つ目)
という感じですね。
jQuery.event.add(l:2437-2515)
でかいんで小分けで書いてきます。
nodeTypeによる対応(l:2437-2439)
nodeTypeはこんな感じ↓
- 1(elementノード)
- 2(attributeノード)
- 3(textノード)
- 4(cdataSectionノード)
- 5(entityReferenceノード)
- 6(entityノード)
- 7(processingInstructionノード)
- 8(commentノード)
- 9(documentノード)
- 10(documentTypeノード)
- 11(documentFragmentノード)
- 12(notationノード)
2437: add: function(elem, types, handler, data) { 2438: if ( elem.nodeType == 3 || elem.nodeType == 8 ) 2439: return;//// ノードが違ったら即リターン
IE対応(l:2441-2444)
コメントでIEがtroubleでうんぬんって書いてあるのできっとまた悪さをしたんですね。
2441: // For whatever reason, IE has trouble passing the window object 2442: // around, causing it to be cloned in the process 2443: if ( elem.setInterval && elem != window ) 2444: elem = window;//// なぜ?
handler(bindで渡したもの)にguidつける(l:2446-2448)
これは後でbindの順番通りに発火するためとか、unbindのときに使えるとか。
2446: // Make sure that the function being executed has a unique ID 2447: if ( !handler.guid ) 2448: handler.guid = this.guid++;//// GUIDつける
要素に対してイベント周りのデータを持たせるための初期化(l:2462-2474)
初回のbind時は必ず||の右側が評価されますね。
2462: // Init the element's event structure 2463: var events = jQuery.data(elem, "events") || jQuery.data(elem, "events", {}), 2464: handle = jQuery.data(elem, "handle") || jQuery.data(elem, "handle", function(){ 2465: // Handle the second event of a trigger and when 2466: // an event is called after a page has unloaded 2467: return typeof jQuery !== "undefined" && !jQuery.event.triggered ? //// あとでよむ 2468: jQuery.event.handle.apply(arguments.callee.elem, arguments) : 2469: undefined; 2470: }); 2471: // Add elem as a property of the handle function 2472: // This is to prevent a memory leak with non-native 2473: // event in IE. 2474: handle.elem = elem; //// l:2514でelemにnullいれてる
event typeのパース(l:2476-2482)
2476: // Handle multiple events separated by a space 2477: // jQuery(...).bind("mouseover mouseout", fn); 2478: jQuery.each(types.split(/\s+/), function(index, type) {//// イベントのタイプはスペース区切りでいっぱいわたせる! 2479: // Namespaced event handlers 2480: var namespaces = type.split(".");//// click.hoge click.fugaとかわたせる 2481: type = namespaces.shift(); 2482: handler.type = namespaces.slice().sort().join(".");//// sliceで新しい配列つくってsortする!sortが破壊的なため!
ここで発見したこと
event typeがliveだったら特別に処理(l:2484-2488)
2484: // Get the current list of functions bound to this event 2485: var handlers = events[type]; 2486: 2487: if ( jQuery.event.specialAll[type] )//// l:2777 live関数 2488: jQuery.event.specialAll[type].setup.call(elem, data, namespaces);
指定されたevent typeが初めてのbindだったときの処理(l:2490-2504)
なんでこんなことする必要があるか調べてみたら、IEはattachEventした順に発火してくれないようですね。
参考 - http://yabooo.org/archives/122
2490: // Init the event handler queue 2491: if (!handlers) { //// 初回だけ 2492: handlers = events[type] = {}; 2493: 2494: // Check for a special event handler 2495: // Only use addEventListener/attachEvent if the special 2496: // events handler returns false //// l:2769 event.specialはreadyのみ //// readyじゃなければ普通にaddEvent、readyならsetupがfalseならaddEventする 2497: if ( !jQuery.event.special[type] || jQuery.event.special[type].setup.call(elem, data, namespaces) === false ) { 2498: // Bind the global event handler to the element //// 実際にDOM要素へのイベントハンドラーの追加(クロスブラウザの処理) 2499: if (elem.addEventListener) 2500: elem.addEventListener(type, handle, false); 2501: else if (elem.attachEvent) 2502: elem.attachEvent("on" + type, handle); 2503: } 2504: }
後処理系(l:2506-2514)
2506: // Add the function to the element's handler list 2507: handlers[handler.guid] = handler;//// guid順に格納することで後で発火順を正しくする 2508: 2509: // Keep track of which events have been used, for global triggering 2510: jQuery.event.global[type] = true; 2511: }); 2512: 2513: // Nullify elem to prevent memory leaks in IE 2514: elem = null;//// いらない参照は片づけましょうね 2515: },
l:2468で呼んでたjQuery.event.handle(l:2665-2707)
これも長いんで小分けで。
まずjavascriptのeventのブラウザ間差異を吸収(l:2665-2670)
2665: handle: function(event) { 2666: // returned undefined or false 2667: var all, handlers; 2668: //// fixはl:2712 2669: event = arguments[0] = jQuery.event.fix( event || window.event ); 2670: event.currentTarget = this;
「event || window.event」という書き方はイベントのクロスブラウザをするときの
イディオムみたいです。
Namespaceのあたりをごにょる(l:2672-2679)
2672: // Namespaced event handlers 2673: var namespaces = event.type.split(".");//// event.type=>"click.hoge.fuga" 2674: event.type = namespaces.shift();//// namespaces=>["hoge","fuga"] 2675: 2676: // Cache this now, all = true means, any handler //// 普通の流れではtrueにはならない。triggerで特殊な時にtrueになる。 2677: all = !namespaces.length && !event.exclusive; 2678: //// "(^|\.)fuga.*\.hoge(\.|$)"になる //// このnamespaceは正規表現オブジェクト 2679: var namespace = RegExp("(^|\\.)" + namespaces.slice().sort().join(".*\\.") + "(\\.|$)");
いろいろ試していたらl:2679の正規表現がどうもなんかおかしい。
ということでBugを調べていたらありました↓
http://dev.jquery.com/ticket/5138
どんなときにおかしくなるかはkyo_agoが詳しく書いてくれています。
http://0-9.sakura.ne.jp/blog/archives/2009/09/14022521.html
レシグも人の子!レシグも人の子!
要素に設定されたハンドラを設定された順に発火するところ(l:2681-2707)
2681: handlers = ( jQuery.data(this, "events") || {} )[event.type]; 2683: for ( var j in handlers ) { 2684: var handler = handlers[j]; 2686: // Filter the functions by class //// namespaceの先頭と末尾が一致する場合 or allがあれば無条件でやる //// 高速にチェックするために正規表現でやってる 2687: if ( all || namespace.test(handler.type) ) { 2688: // Pass in a reference to the handler function itself 2689: // So that we can later remove it 2690: event.handler = handler; 2691: event.data = handler.data; 2692: //// イベントハンドラの中でfalse返せばそれ以上発火しない 2693: var ret = handler.apply(this, arguments); 2695: if( ret !== undefined ){ 2696: event.result = ret; 2697: if ( ret === false ) { 2698: event.preventDefault(); 2699: event.stopPropagation(); 2700: } 2701: } 2703: if( event.isImmediatePropagationStopped() ) 2704: break; 2706: } 2707: }
jQuery.event.fix(l:2712-2759)
2712: fix: function(event) { 2713: if ( event[expando] )//// fix済みなら即リターン 2714: return event; 2715: 2716: // store a copy of the original event object 2717: // and "clone" to set read-only properties 2718: var originalEvent = event; 2719: event = jQuery.Event( originalEvent ); 2720: //// ... 以下略
l:2713のevent[expando]はl:2719でjQuery.Eventオブジェクトでラップする時にtrueになる。
略したところはクロスブラウザでいろいろごにょるところ。
マウスボタンイベント(なにボタンが押されたか)は標準化されてないから使わないでね!
ってコメントがありました。
jQuery.Event(l:2799-2818)
2799: jQuery.Event = function( src ){ 2800: // Allow instantiation without the 'new' keyword 2801: if( !this.preventDefault ) 2802: return new jQuery.Event(src); 2803: 2804: // Event object 2805: if( src && src.type ){ 2806: this.originalEvent = src; 2807: this.type = src.type; 2808: // Event type 2809: }else 2810: this.type = src; 2811: 2812: // timeStamp is buggy for some events on Firefox(#3843) 2813: // So we won't rely on the native value 2814: this.timeStamp = now(); 2815: 2816: // Mark it as fixed 2817: this[expando] = true; 2818: };
l:2801 newしなくてもインスタンシングできるという親切設計。
l:2806 event.originalEventでnativeなeventにアクセスできますね。
例)
$("#hoge").bind("click", function(ev){ var originalEvent = ev.originalEvent; });
l:2814 どうもfirefoxでeventのタイムスタンプがうまく取れてない模様。
l:2817 ここでフラグ立ててjQuery.event.fixのl:2713でチェックに使います。
懇親会
結構がっつり読んで頭が疲れたのでハイパーしゃぶしゃぶタイム!
今回で3回目となる「しゃぶ屋」に行ってきました。
http://r.tabelog.com/tokyo/A1309/A130905/13046339/
やっぱしゃぶ屋のしゃぶはうめぇぜ。
なにやら僕がしゃぶしゃぶに夢中になって食らいついている間に
id:monjudoh、id:seiunsky、@aomushi510 あたりがiPhoneでキャッキャウフフなどする
プチ合コンになっていて非常に疎外感を覚えたのが印象的。
たぶん明日iPhone買う。*3
連絡
次回から参加登録はATNDというサービスを使うことにしました!
みなさんぜひ参加してください!!
http://atnd.org/events/1541
あとtwitterで「#wakateit」というタグをつけるとあとからストーキングされやすくなって
みんな幸せになれるそうなのでタグ入りのコメントをお願いします。
今回からの新しい試みとしてSkypeのオープンチャットを利用して文字ベースでの
参加もできるようにしてみました。*4
参加したい方は直接来ていただくか、僕に言ってもらえれば追加します。
Skypeもcimadaiでやってます。
まとめ
*1:これはsliceの存在が初めわからなかっただけに目から鱗的な感じでした。
*3:やっぱりやめた
*4:id:hagino_3000、id:monjudoh、id:seiunskyありがとうございます。