• 读jQuery之十四(触发事件核心方法)


    触发事件,或称模拟用户动作。比如点击,我们可以用代码去模拟用户点击,达到的效果与真实的鼠标点击是一样的。在 事件模块的演变 我使用了dispatchEvent(标准) 和fireEvent(IE)来主动触发事件。如下
    ...
    dispatch = w3c ?
    	function(el, type){
    		try{
    			var evt = document.createEvent('Event');
    			evt.initEvent(type,true,true);
    			el.dispatchEvent(evt);
    		}catch(e){alert(e)};
    	} :
    	function(el, type){
    		try{
    			el.fireEvent('on'+type);
    		}catch(e){alert(e)}
    	};
    ...
    
     

    jQuery则完全没有用到dispatchEvent/fireEvent方法。它采用的是另外一种机制。

    jQuery触发事件的核心方法是jQuery.event.trigger。它提供给客户端程序员使用的触发事件方法有两个:trigger/triggerHandler

     

    一个事件的发生在某些元素中可能会导致两种动作,一个是默认行为,一个是事件handler。如链接A

    <a href="http://mail.sina.com.cn" onclick="alert(1);">新浪邮箱</a>

    点击后,弹出1(事件handler),点确定跳转(默认行为)到了mail.sina.com.cn。因此,设计的触发事件的函数要考虑到这两种情况。

     

    jQuery使用.trigger和.triggerHandler区分了这两种情况:

    1. trigger 执行事件hanlder/执行冒泡/执行默认行为
    2. triggerHandler 执行事件handler/不冒泡/不执行默认行为
     

    .trigger/.triggerHandler的源码如下

    trigger: function( type, data ) {
    	return this.each(function() {
    		jQuery.event.trigger( type, data, this );
    	});
    },
    
    triggerHandler: function( type, data ) {
    	if ( this[0] ) {
    		return jQuery.event.trigger( type, data, this[0], true );
    	}
    },
    

      

    可以看出,两者都调用jQuery.event.trigger。调用时一个没有传true,一个传了。传了true的triggerHander就表示仅执行事件handler。

    此外还需注意一点区别:trigger是对jQuery对象集合的操作,而triggerHandler仅操作jQuery对象的第一个元素。如下

    <p>p1</p>
    <p>p1</p>
    <p>p1</p>
    <script>
    	$('p').click(function(){alert(1)});
    	$('p').trigger('click'); // 弹3次,即三个p的click都触发了
    	$('p').triggerHandler('click'); // 仅弹1次,即只触发第一个p的click
    </script>

     

    好了,是时候贴出jQuery.event.trigger的代码了

    trigger: function( event, data, elem, onlyHandlers ) {
    	// Event object or event type
    	var type = event.type || event,
    		namespaces = [],
    		exclusive;
    
            ......
    
    }
    

      

    这就是jQuery.event.trigger的定义,省略了大部分。下面一一列举

    if ( type.indexOf("!") >= 0 ) {
    	// Exclusive events trigger only for the exact event (no namespaces)
    	type = type.slice(0, -1);
    	exclusive = true;
    }
    

      

    这一段是为了处理trigger('click!')的情形,即触发非命名空间的事件。变量exclusive挂在事件对象上后在jQuery.event.handle内使用。举个例子

    function fn1() {
    	console.log(1)
    }
    function fn2() {
    	console.log(2)
    }
    $(document).bind('click.a', fn1);
    $(document).bind('click', fn2);
    $(document).trigger('click!'); // 2
    

      

    为document添加了两个点击事件,一个是具有命名空间的"click.a",一个则没有"click"。使用trigger时参数click后加个叹号"!"。从输出结果为2可以看出不触发命名空间的事件。总结一下:

    • trigger('click')   触发所有的点击事件
    • trigger('click.a')  仅触发“click.a” 的点击事件
    • trigger('click!')  触发非命名空间的点击事件

     

    接着看

    if ( type.indexOf(".") >= 0 ) {
    	// Namespaced trigger; create a regexp to match event type in handle()
    	namespaces = type.split(".");
    	type = namespaces.shift();
    	namespaces.sort();
    }

    这段就很好理解了,就是对trigger('click.a')的处理,即对具有命名空间事件的处理。

     

    接着看

    if ( (!elem || jQuery.event.customEvent[ type ]) && !jQuery.event.global[ type ] ) {
    	// No jQuery handlers for this event type, and it can't have inline handlers
    	return;
    }

    对于一些特殊事件如"getData"或对于已经触发过的事件直接返回。

     

    往下

    event = typeof event === "object" ?
    	// jQuery.Event object
    	event[ jQuery.expando ] ? event :
    	// Object literal
    	new jQuery.Event( type, event ) :
    	// Just the event type (string)
    	new jQuery.Event( type );

    有三种情况

    1. event 本身就是jQuery.Event类的实例
    2. event是个普通js对象(非jQuery.Event类的实例)
    3. event是个字符串,如"click"

     

    event.type = type;
    event.exclusive = exclusive;
    event.namespace = namespaces.join(".");
    event.namespace_re = new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.)?") + "(\\.|$)");
    

    需要注意exclusive/namespace/namespace_re挂到了event上了,在jQuery.event.handle中可以用到(事件命名空间)。

     

    往下是

    // triggerHandler() and global events don't bubble or run the default action
    if ( onlyHandlers || !elem ) {
    	event.preventDefault();
    	event.stopPropagation();
    }
    

    onlyHandlers 只在 triggerHandler用到了,即不触发元素的默认行为,且停止冒泡。

     

    下面是

    // Handle a global trigger
    if ( !elem ) {
    	// TODO: Stop taunting the data cache; remove global events and always attach to document
    	jQuery.each( jQuery.cache, function() {
    		// internalKey variable is just used to make it easier to find
    		// and potentially change this stuff later; currently it just
    		// points to jQuery.expando
    		var internalKey = jQuery.expando,
    			internalCache = this[ internalKey ];
    		if ( internalCache && internalCache.events && internalCache.events[ type ] ) {
    			jQuery.event.trigger( event, data, internalCache.handle.elem );
    		}
    	});
    	return;
    }

    这里是个递归调用。如果没有传elem元素,那么从jQuery.cache里取。

     

    接着是

    // Don't do events on text and comment nodes
    if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
    	return;
    }

    属性,文本节点直接返回。

     

    下面是

    // Clone any incoming data and prepend the event, creating the handler arg list
    data = data != null ? jQuery.makeArray( data ) : [];
    data.unshift( event );

    先将参数data放入数组,event对象放在数组的第一个位置。

    接着是

    // Fire event on the current element, then bubble up the DOM tree
    do {
    	var handle = jQuery._data( cur, "handle" );
    
    	event.currentTarget = cur;
    	if ( handle ) {
    		handle.apply( cur, data );
    	}
    
    	// Trigger an inline bound script
    	if ( ontype && jQuery.acceptData( cur ) && cur[ ontype ] && cur[ ontype ].apply( cur, data ) === false ) {
    		event.result = false;
    		event.preventDefault();
    	}
    
    	// Bubble up to document, then to window
    	cur = cur.parentNode || cur.ownerDocument || cur === event.target.ownerDocument && window;
    } while ( cur && !event.isPropagationStopped() );

    这段代码很重要,做了以下事情

    1. 取handle
    2. 执行
    3. 执行通过onXXX方式添加的事件(如onclick="fun()")
    4. 取父元素
    5. while循环不断重复这四步以模拟事件冒泡。直到window对象

    接下是

    // If nobody prevented the default action, do it now
    if ( !event.isDefaultPrevented() ) {
    	var old,
    		special = jQuery.event.special[ type ] || {};
    
    	if ( (!special._default || special._default.call( elem.ownerDocument, event ) === false) &&
    		!(type === "click" && jQuery.nodeName( elem, "a" )) && jQuery.acceptData( elem ) ) {
    
    		// Call a native DOM method on the target with the same name name as the event.
    		// Can't use an .isFunction)() check here because IE6/7 fails that test.
    		// IE<9 dies on focus to hidden element (#1486), may want to revisit a try/catch.
    		try {
    			if ( ontype && elem[ type ] ) {
    				// Don't re-trigger an onFOO event when we call its FOO() method
    				old = elem[ ontype ];
    
    				if ( old ) {
    					elem[ ontype ] = null;
    				}
    
    				jQuery.event.triggered = type;
    				elem[ type ]();
    			}
    		} catch ( ieError ) {}
    
    		if ( old ) {
    			elem[ ontype ] = old;
    		}
    
    		jQuery.event.triggered = undefined;
    	}
    }
    

    这一段是对于浏览器默认行为的触发。如form.submit(),button.click()等。

    注意,由于Firefox中链接的安全性限制,jQuery对链接的默认行为都统一设计为不能触发。即不能通过trigger()使链接跳转。

  • 相关阅读:
    利用border制作三角形原理
    JavaScript中null和undefined
    localStorage 如何存储JSON数据并读取JSON数据
    常见的几种浏览器内核简单介绍
    关于Banner设计的促销氛围
    分数分配
    餐饮管理系统
    餐馆管理之点菜
    餐饮管理之结账
    餐饮管理之菜品管理
  • 原文地址:https://www.cnblogs.com/snandy/p/2129795.html
Copyright © 2020-2023  润新知