jquery.jsを読み解く

第8回 jQueryライブラリ(1795行目~1961行目)

この記事を読むのに必要な時間:およそ 6 分

今回は,jQuery.eventに関する処理の説明になります。ここしばらくは,ブラウザの差異を吸収するコードが続いていましたが,今回のエレメントに対してイベントを割り当てる処理の部分も良くできていて,参考になるような書き方がたくさん出てきます。

jQuery.event.add()

1795: /*
1796:  * A number of helper functions used for managing events.
1797:  * Many of the ideas behind this code orignated from 
1798:  * Dean Edwards' addEvent library.
1799:  */
1800: jQuery.event = {
1801:
1802:  // Bind an event to an element
1803:  // Original by Dean Edwards
1804:  add: function(elem, types, handler, data) {
1805:    if ( elem.nodeType == 3 || elem.nodeType == 8 )
1806:      return;
1807:
1808:    // For whatever reason, IE has trouble passing the window object
1809:    // around, causing it to be cloned in the process
1810:    if ( jQuery.browser.msie && elem.setInterval != undefined )
1811:      elem = window;
1812:
1813:    // Make sure that the function being executed has a unique ID
1814:    if ( !handler.guid )
1815:      handler.guid = this.guid++;
1816:      
1817:    // if data is passed, bind to handler 
1818:    if( data != undefined ) { 
1819:      // Create temporary function pointer to original handler 
1820:      var fn = handler; 
1821:
1822:      // Create unique handler function, wrapped around original handler 
1823:      handler = function() { 
1824:        // Pass arguments and context to original handler 
1825:        return fn.apply(this, arguments); 
1826:      };
1827:
1828:      // Store data in unique handler 
1829:      handler.data = data;
1830:
1831:      // Set the guid of unique handler to the same of original handler, so it can be removed 
1832:      handler.guid = fn.guid;
1833:    }
1834:

それでは,1795行目のコメント文から見ていきましょう。Dean Edwards氏のaddEventライブラリから多くのアイデアをもらっていると書かれています。IE7.jsや/packer/を公開されている方で,Dean Edwards: addEvent() - My Solutionに元になっているソースがあるので,興味がある方はこちらも合わせて読んでみてください。

1804行目からのjQuery.event.add()メソッドは,eventtype(),bind(),one() および bindReady()から呼び出され,実際にイベントを割り当てる内部処理用のメソッドです。1805行目で,引数elemに渡された要素のnodeTypeがテキストノードまたはコメントノードの場合には何もせずにreturnしています。

1810行目では,ブラウザがInternet Explorerの場合に発生する不具合対策のためにwindowオブジェクトのクローンを生成しています。

1814行目は,イベントハンドラに対してユニークなIDを生成しています。ここでいうhandlerというのは,実際に行われる処理を定義した関数のことです。

1818行目からは,引数dataが定義されている場合の処理で,イベントハンドラに対して引数dataをバインド(束縛)します。1823行目でhandlerをラップする関数を作成し,元々のhandlerに引数を渡すように再定義しています。これは,イベントの定義時ではなく,実行時のパラメータをhandlerに対してbindするためです。また,handler.dataにdataをhandler.guidに元のguidをそれぞれ設定します。

jQuery.handle

1835:    // Init the element's event structure
1836:    var events = jQuery.data(elem, "events") || jQuery.data(elem, "events", {}),
1837:      handle = jQuery.data(elem, "handle") || jQuery.data(elem, "handle", function(){
1838:        // returned undefined or false
1839:        var val;
1840:
1841:        // Handle the second event of a trigger and when
1842:        // an event is called after a page has unloaded
1843:        if ( typeof jQuery == "undefined" || jQuery.event.triggered )
1844:          return val;
1845:    
1846:        val = jQuery.event.handle.apply(arguments.callee.elem, arguments);
1847:    
1848:        return val;
1849:      });

1836行目は,events変数の定義です。既にeventsが定義されていればその値を取得,なければ初期化します。

1837行目も同様で,既にhandleが定義されていればその値を取得,なければ新たに定義します。関数の中身は,1838~1848行目にて定義されています。1839行目でundefinedやfalseを返すために,戻り値用のval変数を定義します。そして,ページのアンロード時などjQuery変数が未定義の場合やネイティブイベントが実行中でjQuery.event.triggeredがtrueの場合には,undefinedを返します。

そうでなければ,applyメソッドを利用して関数を実行します。arguments.calleeは自身を指すので,arguments.callee.elemが実行時のthisになります。

1850: // Add elem as a property of the handle function 1851: // This is to prevent a memory leak with non-native 1852: // event in IE. 1853: handle.elem = elem; 1854:

1853行目は,handleのプロパティにelemを代入しています。コメントによると,Internet Explorerでのメモリリークを防ぐためのようです。

1855:      // Handle multiple events seperated by a space
1856:      // jQuery(...).bind("mouseover mouseout", fn);
1857:      jQuery.each(types.split(/\s+/), function(index, type) {
1858:        // Namespaced event handlers
1859:        var parts = type.split(".");
1860:        type = parts[0];
1861:        handler.type = parts[1];
1862:
1863:        // Get the current list of functions bound to this event
1864:        var handlers = events[type];
1865:
1866:        // Init the event handler queue
1867:        if (!handlers) {
1868:          handlers = events[type] = {};
1869:    
1870:          // Check for a special event handler
1871:          // Only use addEventListener/attachEvent if the special
1872:          // events handler returns false
1873:          if ( !jQuery.event.special[type] || jQuery.event.special[type].setup.call(elem) === false ) {
1874:            // Bind the global event handler to the element
1875:            if (elem.addEventListener)
1876:              elem.addEventListener(type, handle, false);
1877:            else if (elem.attachEvent)
1878:              elem.attachEvent("on" + type, handle);
1879:          }
1880:        }
1881:

1857行目からは,イベントハンドラを登録する処理で,jQuery(...).bind("mouseover mouseout", fn); のようにイベント名をスペースで区切って,複数のイベントにハンドラを一括登録できます。処理としては,引数をスペースでsplitしてeach()でループしています。

1859行目は,引数type('click'などが入ります)を'.'で分割しています。これはどういうことかというと,次のようにclickイベントを割り当ててunbindするような処理で,もしclickイベントが複数割り当てられていたら,すべてのclickイベントが解除されてしまいます。

$('.class').bind('click', function(){//whatever});
$('.class').unbind('click');

これを防ぐために,jQueryでは一連のイベントに対して名前を付けておくことができるようにしています。click.namespaceのように記述することでイベントを識別可能になります。

$('.class').bind('click.namespace', function(){//}); 
$('.class').unbind('click.namespace');

1860行目でブラウザが識別できるイベント名をtypeに格納し,1861行目でnamespaceをhandler.typeに格納します。

1864行目は現在割り当てられているイベントハンドラを取得します。そして,もし既に割り当てられているイベントがあれば,初期化を行います。2126行目以降で定義されているready, mouseenter, mouseleave以外なら,addEventListener/attachEventを使って実際に要素にイベントを割り当てます。

1882:        // Add the function to the element's handler list
1883:        handlers[handler.guid] = handler;
1884:
1885:        // Keep track of which events have been used, for global triggering
1886:        jQuery.event.global[type] = true;
1887:      });
1888:    
1889:    // Nullify elem to prevent memory leaks in IE
1890:    elem = null;
1891:  },
1892:

要素のハンドラーリストにハンドラー関数を登録し,後からどのイベントが使われているかを追跡するためにglobal[type]に格納しておきます。最後にInternet Explorerのメモリリーク対策のためにelemを空にします。

著者プロフィール

山下英孝(やましたひでたか)

大学を卒業後,大手SIerに就職し,電機メーカーの研究所勤務を経て,ウノウに入社。1年半に渡ってWebサイトの開発,ディレクション,運用を経験した後に独立。2008年2月よりフリーエンジニアとして活動中。好きな言語はJavaScriptとPythonで,Linuxサーバ運用管理も得意。

ブログWeboo! Returns.

コメント

コメントの記入