jquery.jsを読み解く

第9回jQueryライブラリ (1962行目~2182行目)

今回も前回に引き続き、イベント処理に関する処理の説明になります。

jQuery.event.trigger()

1962:   trigger: function(type, data, elem, donative, extra) {
1963:     // Clone the incoming data, if any
1964:     data = jQuery.makeArray(data || []);
1965: 

jQuery.event.trigger()メソッドは、イベントを実際に実行する処理です。第1引数typeには、"submit"や"click"などのイベントの種類を指定します。また、第2引数dataには、イベントハンドラに渡すデータを配列として指定します。第3引数elemは、イベントの動作対象となる要素です。第4引数donativeは、後ほど出てきますがブラウザのネイティブなイベント動作を行うかどうかのフラグです。そして、最後にextraが追加で実行する処理を指定します。

1964行では、引数として渡されたdataの複製を作成します。dataが指定されていなければ、空の配列を設定します。

1966:     // Handle a global trigger
1967:     if ( !elem ) {
1968:       // Only trigger if we've ever bound an event for it
1969:       if ( this.global[type] )
1970:         jQuery("*").add([window, document]).trigger(type, data);
1971: 

1967行目からは、第3引数elemが指定されていない場合の処理です。もし、グローバルなイベントが登録されていれば、window.documentに対してtypeイベントを実行します。

1972:     // Handle triggering a single element
1973:     } else {
1974:       // don't do events on text and comment nodes
1975:       if ( elem.nodeType == 3 || elem.nodeType == 8 )
1976:         return undefined;
1977: 
1978:       var val, ret, fn = jQuery.isFunction( elem[ type ] || null ),
1979:         // Check to see if we need to provide a fake event, or not
1980:         event = !data[0] || !data[0].preventDefault;
1981:       
1982:       // Pass along a fake event
1983:       if ( event )
1984:         data.unshift( this.fix({ type: type, target: elem }) );
1985: 
1986:       // Enforce the right trigger type
1987:       data[0].type = type;
1988: 

1975行目は、もうお馴染みの処理となりましたコメントノードとテキストノードの場合には、処理を行わないための条件式です。1978行目は、各変数を初期化しています。fnは引数として指定されたelem[type]が関数オブジェクトかどうかのチェック、eventはdata配列の第1要素にpreventDefaultというプロパティを持っているかどうかを判定しています。このpreventDefaultが指定されていなければ、1984行目にてダミーのイベントを準備します。1987行目は、イベントの種類をdata[0]に格納しています。

1989:       // Trigger the event
1990:       if ( jQuery.isFunction( jQuery.data(elem, "handle") ) )
1991:         val = jQuery.data(elem, "handle").apply( elem, data );
1992: 
1993:       // Handle triggering native .onfoo handlers
1994:       if ( !fn && elem["on"+type] && elem["on"+type].apply( elem, data ) === false )
1995:         val = false;
1996: 

1990行目は、elemに対応するhandleが定義されていて、それが関数であればそれを実行して結果をvalに格納します。1994行目は、先ほどのelem[type]が直接実行できる関数ではない場合に、elem[on~]が実行できればそれを実行します。そして、戻り値がfalseであればvalにfalseを格納します。

1997:       // Extra functions don't get the custom event object
1998:       if ( event )
1999:         data.shift();
2000: 
2001:       // Handle triggering of extra function
2002:       if ( extra && jQuery.isFunction( extra ) ) {
2003:         // call the extra function and tack the current return value on the end for possible inspection
2004:         ret = extra.apply( elem, val == null ? data : data.concat( val ) );
2005:         // if anything is returned, give it precedence and have it overwrite the previous value
2006:         if (ret !== undefined)
2007:           val = ret;
2008:       }
2009: 

ダミーのイベントが登録されていれば、1999行目にてそれを削除します。

2002行目は、追加の関数が指定されている場合にそれを実行します。もし、valがnullならばdataを、あればdataとvalを結合して引数に指定します。そして、その戻り値ををretに格納します。

retにundefined以外の値が格納されていたら、2007行目にてvalにその値をセットしなおします。

2010:       // Trigger the native events (except for clicks on links)
2011:       if ( fn && donative !== false && val !== false && !(jQuery.nodeName(elem, 'a') && type == "click") ) {
2012:         this.triggered = true;
2013:         try {
2014:           elem[ type ]();
2015:         // prevent IE from throwing an error for some hidden elements
2016:         } catch (e) {}
2017:       }
2018: 
2019:       this.triggered = false;
2020:     }
2021: 

2010行目からは、リンクをクリックする以外のブラウザのネイティブなイベントを処理する部分になります。fnが定義済みで、donativeがfalse以外で、valがfalse以外で、aタグ要素のクリックイベント以外の場合に、this.triggeredの値をtrueにしてelem[ type ]()メソッドを実行します。

donativeの値によってネイティブなイベント動作、つまりブラウザのデフォルト動作を行うかどうかを決定しています。これは、jQuery.trigger()メソッドから呼び出された場合にはネイティブなイベントを実行しますが、jQuery.triggerHandler()メソッドから呼び出された場合はdonativeの値がfaleになるため、ブラウザのデフォルト動作は実行されないようになっています。

2022:     return val;
2023:   },
2024: 

最後に2022行目で戻り値としてvalを返して終了です。

jQuery.event.handle()

2025行目からのjQuery.event.handle()メソッドは、イベント発生時に対応するハンドラを取り出してきて実行するメソッドです。

2025:   handle: function(event) {
2026:     // returned undefined or false
2027:     var val;
2028: 
2029:     // Empty object is for triggered events with no data
2030:     event = jQuery.event.fix( event || window.event || {} ); 
2031: 
2032:     // Namespaced event handlers
2033:     var parts = event.type.split(".");
2034:     event.type = parts[0];
2035: 
2036:     var handlers = jQuery.data(this, "events") && jQuery.data(this, "events")[event.type], args = Array.prototype.slice.call( arguments, 1 );
2037:     args.unshift( event );
2038: 

2027行目は、戻り値用の変数初期化です。

2029行目は、引数eventもしくはwindow.eventを後述のjQuery.event.fix()メソッドを通すことでブラウザ間の違いを吸収しています。

2033行目は、名前空間指定のイベントに関する処理で、foo.bar書式のfooの部分、すなわちイベント種別のみを取り出しています。

2036行目は、現在処理中の要素に登録されたイベント(events⁠⁠、もしくはそのイベントのevent.typeをhandler変数に代入します。また、argsに配列の2番目以降の要素を格納します。そして、2037行目で先ほどfix()を行ったeventを1番目に再代入しています。

2039:     for ( var j in handlers ) {
2040:       var handler = handlers[j];
2041:       // Pass in a reference to the handler function itself
2042:       // So that we can later remove it
2043:       args[0].handler = handler;
2044:       args[0].data = handler.data;
2045: 
2046:       // Filter the functions by class
2047:       if ( !parts[1] || handler.type == parts[1] ) {
2048:         var ret = handler.apply( this, args );
2049: 
2050:         if ( val !== false )
2051:           val = ret;
2052: 
2053:         if ( ret === false ) {
2054:           event.preventDefault();
2055:           event.stopPropagation();
2056:         }
2057:       }
2058:     }
2059: 

2039行目は、先ほど取得したハンドラ変数に対して処理を行います。2040行目でハンドラを取得し、後で削除できるように自身の変数にhandlerおよびdataを格納しておきます。

2047行目以降は、名前空間が指定されていないイベント、もしくは名前空間が現在処理中のhandlerと等しい場合に処理を行います。2048行目で実際にargsを引数としてhandler関数を実行し、ret変数に結果を格納します。retがfalseならば、fix()メソッドの実行時に定義されたpreventDefault()およびstopPropagation()メソッドを実行します。この2つのメソッドについては後ほど説明しますが、ブラウザのデフォルト動作を抑えるのと、コピーしたオブジェクトでもイベントが実行されるのを防ぐための処理です。

2060:     // Clean up added properties in IE to prevent memory leak
2061:     if (jQuery.browser.msie)
2062:       event.target = event.preventDefault = event.stopPropagation =
2063:         event.handler = event.data = null;
2064: 
2065:     return val;
2066:   },
2067: 

2060行目からは、コメント文にもあるようにInternet Explorerのメモリーリークに対する対策です。event.target, event.preventDefault, event.stopPropagation, event.handler, event.dataにnullを代入しています。

2065行目にて、最後に結果のvalを返します。

jQuery.event.fix()

jQuery.event.fix()メソッドは、クロスブラウザを実現するためのメソッドです。

2068:   fix: function(event) {
2069:     // store a copy of the original event object 
2070:     // and clone to set read-only properties
2071:     var originalEvent = event;
2072:     event = jQuery.extend({}, originalEvent);
2073:     
2074:     // add preventDefault and stopPropagation since 
2075:     // they will not work on the clone
2076:     event.preventDefault = function() {
2077:       // if preventDefault exists run it on the original event
2078:       if (originalEvent.preventDefault)
2079:         originalEvent.preventDefault();
2080:       // otherwise set the returnValue property of the original event to false (IE)
2081:       originalEvent.returnValue = false;
2082:     };
2083:     event.stopPropagation = function() {
2084:       // if stopPropagation exists run it on the original event
2085:       if (originalEvent.stopPropagation)
2086:         originalEvent.stopPropagation();
2087:       // otherwise set the cancelBubble property of the original event to true (IE)
2088:       originalEvent.cancelBubble = true;
2089:     };
2090:     
2091:     // Fix target property, if necessary
2092:     if ( !event.target )
2093:       event.target = event.srcElement || document; // Fixes #1925 where srcElement might not be defined either
2094:         
2095:     // check if target is a textnode (safari)
2096:     if ( event.target.nodeType == 3 )
2097:       event.target = originalEvent.target.parentNode;
2098: 
2099:     // Add relatedTarget, if necessary
2100:     if ( !event.relatedTarget && event.fromElement )
2101:       event.relatedTarget = event.fromElement == event.target ? event.toElement : event.fromElement;
2102: 
2103:     // Calculate pageX/Y if missing and clientX/Y available
2104:     if ( event.pageX == null && event.clientX != null ) {
2105:       var doc = document.documentElement, body = document.body;
2106:       event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc.clientLeft || 0);
2107:       event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc.clientTop || 0);
2108:     }
2109:       
2110:     // Add which for key events
2111:     if ( !event.which && ((event.charCode || event.charCode === 0) ? event.charCode : event.keyCode) )
2112:       event.which = event.charCode || event.keyCode;
2113:     
2114:     // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs)
2115:     if ( !event.metaKey && event.ctrlKey )
2116:       event.metaKey = event.ctrlKey;
2117: 
2118:     // Add which for click: 1 == left; 2 == middle; 3 == right
2119:     // Note: button is not normalized, so don't use it
2120:     if ( !event.which && event.button )
2121:       event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) ));
2122:       
2123:     return event;
2124:   },
2125:   

2071~2071行目は、元のイベントをoriginalEvent変数に格納し、jQuery.extend()メソッドを使って再度event変数に代入することで、読み込み専用のプロパティとして設定します。

2076~2082行目は、今までに何度か登場しているevent.preventDefault()メソッドで、複製したオブジェクトにおいてブラウザのデフォルト動作が行われるのを抑制します。こちらもInternet Explorerのための処理です。

2083行目~2089行目は、event.stopPropagation()メソッドで、originalEventのメソッドを実行した後にcancelBubbleをtrueに設定します。こちらもInternet Explorerのための処理です。

2092行目~2093行目は、event.targetが未設定の場合にevent.srcElementが存在すればevent.srcElementを、なければdocumentオブジェクトをevent.targetに設定します。

2096~2097行目は、ターゲットがテキストノードの場合にtargetを親ノードに変更します。Safariブラウザ用の対策になります。

2100行目~2101行目は、relatedTargetがなくてfromElementが存在する場合にevent.relatedTargetの値を設定します。

2104行目~2108行目は、pageXが存在せずにclientXプロパティがあるようなブラウザ(つまりInternet Explorer)の場合にpageXおよびpageYの値を計算します。

2111行目~2112行目は、マウスクリックやキー押下時のイベントをevent.whichに統一します。

2115行目~2116行目は、Mac以外でctrlKeyのイベントをmetaKeyとして取得できるようにします。

2120行目~2121行目は、マウスのどのボタンが押されたかをevent.whichとして取得できるようにします。

jQuery.event.special.ready

2126行目からは、jQuery.event.specialというイベント処理の中でも特別な処理をまとめた部分になります。jQuery.event.specialにはready,mouseenter,mouseleaveの3つの属性があり、それぞれsetup(),teardown(),handler()という3つのメソッドを持っています(ただし、readyにhandlerはありません⁠⁠。setup()はイベント登録用、teardown()はイベント解除用、handler()はイベントハンドラー用のメソッドになります。それでは、これらのメソッドを順番に見ていきましょう。

2126:   special: {
2127:     ready: {
2128:       setup: function() {
2129:         // Make sure the ready event is setup
2130:         bindReady();
2131:         return;
2132:       },
2133:       
2134:       teardown: function() { return; }
2135:     },
2136:

2128行目のsetup()メソッドは、2284行目で定義されているbindready()メソッドを呼び出します。イベント解除を行うteardown()では特になにもせずに、そのままreturnします。

jQuery.event.special.mouseenter

2137:     mouseenter: {
2138:       setup: function() {
2139:         if ( jQuery.browser.msie ) return false;
2140:         jQuery(this).bind("mouseover", jQuery.event.special.mouseenter.handler);
2141:         return true;
2142:       },
2143:     
2144:       teardown: function() {
2145:         if ( jQuery.browser.msie ) return false;
2146:         jQuery(this).unbind("mouseover", jQuery.event.special.mouseenter.handler);
2147:         return true;
2148:       },
2149:       
2150:       handler: function(event) {
2151:         // If we actually just moused on to a sub-element, ignore it
2152:         if ( withinElement(event, this) ) return true;
2153:         // Execute the right handlers by setting the event type to mouseenter
2154:         arguments[0].type = "mouseenter";
2155:         return jQuery.event.handle.apply(this, arguments);
2156:       }
2157:     },
2158:   

2137行目からのjQuery.event.special.mouseenterは、setup()メソッドでInternet Explorer以外の場合に指定要素の"mouseover"イベントにハンドラを割り当てます。teardown()でも同様にunbindでイベント登録を解除します。2150行目からのhandler()メソッドは、実際のイベントハンドラの定義になります。2152行目にてwithinElement()関数を呼び出しているのは、内包している要素があった場合に通常ですとその上でマウスを移動させるとmouseover/outイベントが発生してしまいますが、それを防ぐための処理です。そして、2155行目で引数にmouseenterを指定した上で、jQuery.event.handleメソッドを呼び出します。

Internet Explorerの場合は、attachEventおよびdetachEventを利用するため、ここでは何もせずにfalseを返します。

jQuery.event.special.mouseleave

2159:     mouseleave: {
2160:       setup: function() {
2161:         if ( jQuery.browser.msie ) return false;
2162:         jQuery(this).bind("mouseout", jQuery.event.special.mouseleave.handler);
2163:         return true;
2164:       },
2165:     
2166:       teardown: function() {
2167:         if ( jQuery.browser.msie ) return false;
2168:         jQuery(this).unbind("mouseout", jQuery.event.special.mouseleave.handler);
2169:         return true;
2170:       },
2171:       
2172:       handler: function(event) {
2173:         // If we actually just moused on to a sub-element, ignore it
2174:         if ( withinElement(event, this) ) return true;
2175:         // Execute the right handlers by setting the event type to mouseleave
2176:         arguments[0].type = "mouseleave";
2177:         return jQuery.event.handle.apply(this, arguments);
2178:       }
2179:     }
2180:   }
2181: };
2182: 

2159行目からのjQuery.event.special.mouseleaveもmouseenterと同様です。setup()メソッドでInternet Explorer以外の場合に指定要素の"mouseout"イベントにハンドラを割り当てます。teardown()ではunbindを使ってイベント登録を解除します。2172行目からのhandler()メソッドは、実際のイベントハンドラの定義になります。2174行目にてwithinElement()関数を呼び出しているのもmouseenterの時と同様で、内包している要素があった場合に通常ですととその上でマウスを移動させるとmouseover/outイベントが発生してしまいますが、それを防ぐための処理です。そして、2177行目で引数にmouseleaveを指定した上で、jQuery.event.handleメソッドを呼び出します。

Internet Explorerの場合は、attachEventおよびdetachEventを利用するため、ここでは何もせずにfalseを返します。

おすすめ記事

記事・ニュース一覧