しまてく

学んだ技術を書きためるブログ

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が破壊的なため!

ここで発見したこと

  1. l:2477にもありますが、$(...).bind("mouseover mouseout", fn)で一度に複数のイベントに対して同じハンドラを追加できること
  2. 「.」区切りでオリジナルなネームスペースを作れること
  3. arrayObject.slice().sort()で非破壊的なソートができること*1
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:         }

配列の中に含むかどうかは正規表現でチェックするのがいいようですね!
*2

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:monjudohid:seiunsky、@aomushi510 あたりがiPhoneでキャッキャウフフなどする
プチ合コンになっていて非常に疎外感を覚えたのが印象的。
たぶん明日iPhone買う。*3

連絡

次回から参加登録はATNDというサービスを使うことにしました!
みなさんぜひ参加してください!!
http://atnd.org/events/1541


あとtwitterで「#wakateit」というタグをつけるとあとからストーキングされやすくなって
みんな幸せになれるそうなのでタグ入りのコメントをお願いします。


今回からの新しい試みとしてSkypeのオープンチャットを利用して文字ベースでの
参加もできるようにしてみました。*4
参加したい方は直接来ていただくか、僕に言ってもらえれば追加します。
Skypeもcimadaiでやってます。

まとめ

  1. スペース区切りで一度に複数のイベントに対して同じハンドラを追加できる
  2. 「.」区切りでオリジナルなネームスペースを作れる
  3. arrayObject.slice().sort()で非破壊的なソートができる
  4. 配列の中に任意の値を含むかどうかは正規表現でチェックする
  5. コードリーディングにはチャットが相性がいい
  6. safariのWebインスペクタは補完が効くのでFirebugより書きやすいかも
  7. iPhoneは合コンアイテムになりえる
  8. pokenは流行らない

*1:これはsliceの存在が初めわからなかっただけに目から鱗的な感じでした。

*2:正規表現に関しては十分注意しないといけないけど。

*3:やっぱりやめた

*4:id:hagino_3000id:monjudohid:seiunskyありがとうございます。